Embedded Form Testing Guide

How to Test Airtable Form Embed with Playwright: Iframes, Attachments, and Prefill

A scenario-by-scenario walkthrough of testing embedded Airtable forms with Playwright. Covers iframe traversal, attachment field uploads, linked record lookups, form submission confirmation, and prefilling fields via URL parameters.

300K+

Over 300,000 organizations use Airtable to power workflows, and embedded forms are one of the most common ways they collect external data from users who never log into Airtable directly.

Airtable

0Iframe boundary to cross
0Field types covered
0Test scenarios
0%Fewer lines with Assrt

Airtable Embedded Form Submission Flow

User BrowserHost PageAirtable IframeAirtable APIAirtable BaseLoad host page with embedRender iframe (airtable.com/embed/...)Fill form fieldsAttach file via upload inputPOST form data + attachmentCreate record in baseReturn confirmationShow success message

1. Why Testing Airtable Form Embeds Is Harder Than It Looks

Airtable forms look simple. You embed an iframe, your users fill in fields, they hit submit, and a new record appears in your base. But from a test automation perspective, every layer of that flow presents a distinct challenge. The first obstacle is the iframe itself. Airtable serves the form from airtable.com/embed/..., which means Playwright cannot interact with the form elements through the main page context. You need to use frameLocator() or frame() to drill into the cross-origin iframe, and even then some interactions behave differently inside the iframe boundary.

The second challenge is field type diversity. Airtable forms can contain single-line text, long text, single select dropdowns, multi-select tags, date pickers, checkboxes, phone numbers, email fields, URLs, number fields, currency fields, attachment uploads, and linked record lookups. Each field type has its own DOM structure inside the Airtable form renderer. A single select dropdown is not a native <select> element; it is a custom React component with a popover menu that requires clicking to open, waiting for the popover to render, and then clicking the correct option.

Third, attachment fields use a file upload input that is nested inside the iframe. Playwright's setInputFiles()works on native file inputs, but you need to locate the hidden input within Airtable's custom upload component. Fourth, linked record fields present a search interface where you type a query, wait for Airtable to fetch matching records from the linked table, and then select one from the results. This involves an async network call inside the iframe that your test must wait for. Fifth, prefill via URL parameters requires constructing the embed URL with specific query string parameters that map to field names, and verifying that the form renders with those values pre-populated.

Airtable Form Embed Challenges

📦

Iframe Boundary

Cross-origin embed from airtable.com

🌐

Custom Fields

Non-native selects, date pickers, tags

⚙️

File Uploads

Hidden input inside custom component

🔔

Linked Records

Async search with network roundtrip

↪️

URL Prefill

Query params map to field names

Confirmation

Success message inside iframe

A robust Airtable form embed test suite must handle all of these surfaces. The sections below walk through each scenario with runnable Playwright TypeScript you can paste into your project and adapt to your specific base and form configuration.

2. Setting Up Your Test Environment

Before writing tests, you need an Airtable form you control. Create a dedicated test base in Airtable with a table that includes representative field types: a single-line text field for “Name,” a single select for “Department,” an email field, a date field, an attachment field, and a linked record field that points to a second table with a few sample records. Create a form view on this table and enable the embed option. You will use the share link and the embed link throughout these tests.

Airtable Test Environment Checklist

  • Create a dedicated Airtable base for testing (separate from production)
  • Add a table with text, select, email, date, attachment, and linked record fields
  • Create a form view and enable sharing/embedding
  • Populate the linked table with 3-5 sample records for lookup tests
  • Generate a Personal Access Token with data.records:write scope
  • Create a host page that embeds the Airtable form iframe
  • Prepare sample attachment files (a small PNG and a PDF under 5MB)
  • Note the form URL, base ID, table ID, and field names for environment variables

Environment Variables

.env.test

Host Page Setup

Your host page is the page that contains the Airtable form embed. In production this is your marketing site, internal tool, or landing page. For testing, you need a minimal HTML page or a route in your app that renders the iframe. The key detail is the iframe src attribute, which is the Airtable embed URL.

test/fixtures/host-page.html

Playwright Configuration

Airtable forms load asynchronously inside the iframe, and the iframe content comes from a different origin. Set a generous timeout for navigation and use the permissions option if your tests need clipboard access for paste operations. The critical configuration is allowing Playwright to interact with cross-origin iframes, which it does by default as long as the iframe is same-process (Chromium renders cross-origin iframes in the same renderer when they are embedded via <iframe>).

playwright.config.ts

Cleanup Helper: Delete Test Records via API

Each test run creates records in your Airtable base. To keep the test environment clean, use the Airtable REST API to delete records created during the test. The helper below finds records matching a test marker and removes them.

test/helpers/airtable-cleanup.ts
Install Dependencies

3. Scenario: Accessing the Form Inside the Iframe

The foundational step for every Airtable form test is getting a reference to the iframe content. Airtable embeds render inside an iframe pointing to airtable.com/embed/shrXXX. Playwright provides frameLocator()to interact with elements inside iframes without needing to switch contexts manually. The frame locator returns a scoped locator that targets elements within the iframe's DOM tree.

The critical detail is waiting for the iframe content to fully load before interacting with form fields. The Airtable form renderer is a React application that bootstraps asynchronously after the iframe loads. You need to wait for a visible form element (like the first input label or the submit button) to confirm the form is interactive.

1

Accessing the Airtable Form Iframe

Straightforward

Goal

Navigate to your host page, locate the Airtable form iframe, and confirm the form has loaded and is interactive.

Preconditions

  • Host page is running at HOST_PAGE_URL
  • Airtable form embed URL is valid and publicly accessible
  • The form contains at least one visible text input field

Playwright Implementation

airtable-iframe.spec.ts

What to Assert Beyond the UI

  • The iframe src contains your specific form share ID (shr...)
  • The form field count matches your expected configuration
  • Required field indicators are visible on mandatory fields

4. Scenario: Basic Text and Select Field Submission

The simplest form submission test fills text inputs and selects a value from a dropdown. Airtable form fields are rendered with custom components, not native HTML form elements. Text inputs use a contenteditable div or a styled input, and single select fields use a custom dropdown popover. You interact with them through Playwright locators scoped to the frame locator you established in the previous scenario.

Airtable forms label each field with the column name from your table. If your table has a “Name” column, the form renders a label with that text. You can use getByLabel() scoped to the frame locator, or target the field container by the field name text and then locate the input within it. The second approach is more reliable because some Airtable field types do not use native label/input associations.

2

Text and Single Select Submission

Straightforward

Playwright Implementation

airtable-basic-submit.spec.ts

Basic Submission: Playwright vs Assrt

import { test, expect } from '@playwright/test';

test('submit text and single select fields', async ({ page }) => {
  await page.goto('/contact');
  const frame = page.frameLocator('iframe[src*="airtable.com/embed"]');
  await expect(
    frame.getByRole('button', { name: /submit/i })
  ).toBeVisible({ timeout: 15_000 });

  const nameField = frame
    .locator('.sharedFormField')
    .filter({ hasText: 'Name' })
    .locator('input, [contenteditable="true"]')
    .first();
  await nameField.fill(`E2E Test ${Date.now()}`);

  const emailField = frame
    .locator('.sharedFormField')
    .filter({ hasText: 'Email' })
    .locator('input')
    .first();
  await emailField.fill(`e2e-test+${Date.now()}@example.com`);

  const deptField = frame
    .locator('.sharedFormField')
    .filter({ hasText: 'Department' });
  await deptField.locator('[role="combobox"]').first().click();
  await frame.locator('[role="option"]')
    .filter({ hasText: 'Engineering' }).click();

  await frame.getByRole('button', { name: /submit/i }).click();
  await expect(
    frame.getByText(/thank you/i)
  ).toBeVisible({ timeout: 10_000 });
});
57% fewer lines

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started

5. Scenario: Attachment Field Upload

Attachment fields in Airtable forms allow users to upload files directly into a record. The form renders a custom upload component with a hidden file input. In Playwright, you use setInputFiles() to programmatically attach files, but the trick is finding the actual <input type="file"> element inside Airtable's upload component. The input is typically hidden with CSS and nested inside a wrapper div.

Airtable uploads the file to its own storage before the form is submitted. When you call setInputFiles(), the Airtable form JavaScript intercepts the change event, uploads the file in the background, and displays a thumbnail or filename preview in the attachment field. Your test needs to wait for this upload to complete before clicking submit, otherwise the submission may not include the attachment.

3

File Attachment Upload

Complex

Goal

Upload a file to an Airtable form attachment field via the embedded iframe, confirm the upload preview appears, submit the form, and verify the record in Airtable contains the attachment.

Playwright Implementation

airtable-attachment.spec.ts

Verify via Airtable API

After submission, use the Airtable REST API to confirm the record was created with the correct attachment. This is especially important because the form confirmation message does not tell you whether the file was actually attached to the record.

test/helpers/verify-attachment.ts

6. Scenario: Linked Record Lookup Fields

Linked record fields in Airtable forms present a searchable dropdown that queries records from another table in your base. When a user clicks on a linked record field, Airtable renders a search input. As the user types, the form fetches matching records from the linked table and displays them as selectable options. This involves an asynchronous network call inside the iframe, which makes it one of the trickier field types to automate.

The automation pattern is: click to open the linked record picker, type a search query, wait for the results to load, and click the correct option. The wait step is critical because Airtable debounces the search input and makes a network request to fetch results. If you click before results appear, you will get a flaky test. Use waitForSelector or an expect with a timeout on the result option to handle the async fetch.

4

Linked Record Lookup Selection

Complex

Playwright Implementation

airtable-linked-record.spec.ts

Linked Record Selection: Playwright vs Assrt

test('select a linked record in the form', async ({ page }) => {
  await page.goto('/contact');
  const frame = page.frameLocator('iframe[src*="airtable.com/embed"]');
  await expect(
    frame.getByRole('button', { name: /submit/i })
  ).toBeVisible({ timeout: 15_000 });

  const nameField = frame
    .locator('.sharedFormField').filter({ hasText: 'Name' })
    .locator('input').first();
  await nameField.fill(`E2E Test Linked ${Date.now()}`);

  const linkedField = frame
    .locator('.sharedFormField').filter({ hasText: 'Project' });
  await linkedField.locator('[role="combobox"]').first().click();
  await linkedField.locator('input').first().fill('Website Redesign');

  const result = frame.locator('[role="option"]')
    .filter({ hasText: 'Website Redesign' });
  await expect(result).toBeVisible({ timeout: 10_000 });
  await result.click();

  await frame.getByRole('button', { name: /submit/i }).click();
  await expect(
    frame.getByText(/thank you/i)
  ).toBeVisible({ timeout: 10_000 });
});
57% fewer lines

7. Scenario: Prefilling Fields via URL Parameters

Airtable forms support prefilling field values through URL query parameters. This is commonly used when you link to a form from an email campaign, a CRM, or an internal tool and want certain fields pre-populated. The URL format uses prefill_FieldName=Value parameters appended to the form URL. The field name in the parameter must exactly match the column name in your Airtable table, with spaces encoded as + or %20.

Testing prefill requires modifying the iframe src to include the query parameters, then verifying that the form renders with those values already populated. This tests both your URL construction logic and Airtable's prefill parsing. Note that prefill only works on certain field types: text, email, URL, phone, number, currency, single select, and date. It does not work for attachment fields or linked record fields.

URL Prefill Flow

🌐

Construct URL

Add prefill_Name=John&prefill_Email=...

📦

Load Iframe

Airtable parses query params

Populate Fields

Matching fields get pre-filled values

🌐

User Reviews

Some fields already filled

⚙️

Submit

Record created with prefilled + manual values

5

Prefill Form Fields via URL Parameters

Moderate

Playwright Implementation

airtable-prefill.spec.ts

8. Scenario: Verifying Submission Confirmation

After a successful form submission, Airtable replaces the form with a confirmation message. The default message is “Your submission has been received!” but form owners can customize this text. Additionally, Airtable can be configured to show a “Submit another response” button that reloads the form for another submission. Your test should verify both the confirmation message and, if applicable, that submitting another response resets all fields.

The confirmation state also provides a signal for your end-to-end validation. After seeing the confirmation, you can query the Airtable API to verify the record exists with the expected field values. This closes the loop between the UI interaction and the data layer, catching bugs where the form appears to succeed but the record is malformed or missing fields.

6

Submission Confirmation and Re-submit

Moderate

Playwright Implementation

airtable-confirmation.spec.ts

Confirmation + API Verification: Playwright vs Assrt

test('verify record via Airtable API after submission', async ({ page }) => {
  const testName = `E2E Test Verify ${Date.now()}`;
  await page.goto('/contact');
  const frame = page.frameLocator('iframe[src*="airtable.com/embed"]');
  await expect(
    frame.getByRole('button', { name: /submit/i })
  ).toBeVisible({ timeout: 15_000 });

  const nameField = frame.locator('.sharedFormField')
    .filter({ hasText: 'Name' }).locator('input').first();
  await nameField.fill(testName);

  await frame.getByRole('button', { name: /submit/i }).click();
  await expect(
    frame.getByText(/thank you/i)
  ).toBeVisible({ timeout: 10_000 });

  // Wait for Airtable to process, then verify via API
  await page.waitForTimeout(3000);
  const apiRes = await fetch(
    `https://api.airtable.com/v0/${BASE_ID}/${TABLE_ID}?filterByFormula=...`,
    { headers: { Authorization: `Bearer ${PAT}` } }
  );
  const { records } = await apiRes.json();
  expect(records).toHaveLength(1);
  expect(records[0].fields['Name']).toBe(testName);
});
55% fewer lines

9. Common Pitfalls That Break Airtable Form Tests

The most common failure mode in Airtable form testing is interacting with elements before the iframe content has loaded. Airtable's form renderer is a React application that bootstraps asynchronously. If you try to fill a field 200ms after the iframe element appears in the DOM, the internal React app may not have mounted yet. Always wait for a known interactive element (like the submit button) before interacting with any form field.

The second frequent pitfall is selector fragility. Airtable does not guarantee stable CSS class names or data attributes on its form elements. Classes like .sharedFormField and .formSubmitButton have been stable for years, but Airtable can change them in any deploy. Prefer role-based selectors (getByRole) and text-based selectors (getByText, filter({ hasText })) over CSS class selectors where possible.

Airtable Form Test Anti-Patterns

  • Interacting with form fields before the iframe React app has mounted
  • Using CSS class selectors that Airtable can change without notice
  • Forgetting to scope locators to the frameLocator (targeting host page DOM instead)
  • Not waiting for linked record search results before clicking an option
  • Clicking submit before an attachment upload completes in the background
  • Using identical record names across test runs without cleanup, causing filter collisions
  • Expecting prefill to work on attachment or linked record fields (it does not)
  • Running too many form submissions in parallel, hitting Airtable's 5 requests/sec rate limit

Iframe Loading Race Condition

A common Stack Overflow question is “why does my Airtable form test work locally but fail in CI?” The answer is almost always timing. In CI, network latency to Airtable's servers is higher, and the iframe content takes longer to load. A test that uses a fixed 2-second wait locally needs a 10-second wait in CI. Replace fixed waits with explicit assertions on visible elements. The submit button visibility check is the most reliable gate because Airtable renders it last.

Common CI Failure: Iframe Not Loaded

Attachment Upload Timing

When you call setInputFiles() on the hidden file input, Airtable starts uploading the file to its storage in the background. The upload can take 2 to 15 seconds depending on file size and network speed. If you click submit before the upload finishes, the form submits without the attachment. Always wait for the upload preview (thumbnail or filename) to appear before proceeding.

Rate Limiting on the Airtable API

Airtable enforces a rate limit of 5 requests per second per base. If your test suite runs cleanup queries, record verification, and form submissions in rapid succession, you can hit this limit and get 429 responses. Add a small delay between API calls in your cleanup helper, or batch your cleanup into a single delete call at the end of the test suite rather than after each individual test.

Full Airtable Form Test Suite Run

10. Writing These Scenarios in Plain English with Assrt

Every scenario above requires detailed knowledge of Airtable's internal DOM structure: custom class names for form fields, hidden file inputs inside upload components, role attributes on dropdown popovers, and the async search behavior of linked record pickers. When Airtable deploys a new version of their form renderer (which happens without notice), any of these selectors can break. Assrt lets you describe the scenario in plain English and generates the equivalent Playwright code, regenerating selectors automatically when the underlying page changes.

The attachment upload scenario from Section 5 is a perfect example. In raw Playwright, you need to find the hidden file input inside Airtable's custom upload component, call setInputFiles(), and wait for the upload preview. In Assrt, you describe the intent: “attach a file to the Attachment field” and the framework resolves the mechanism at runtime.

scenarios/airtable-form-suite.assrt

Assrt compiles each scenario block into the same Playwright TypeScript you saw in the preceding sections, committed to your repo as real tests you can read, run, and modify. When Airtable renames a CSS class, restructures the upload component, or changes how linked record search results render, Assrt detects the failure, analyzes the new DOM, and opens a pull request with the updated locators. Your scenario files stay untouched.

Start with the basic text submission scenario. Once it is green in your CI, add the attachment upload, then the linked record selection, then the prefill verification, then the confirmation and re-submit flow. Within a single afternoon you can have complete Airtable form embed coverage that catches regressions before your users encounter broken forms on your site.

Related Guides

Ready to automate your testing?

Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.

$npm install @assrt/sdk