From b365bf820b6ecce83f022e5c708338ee1761d485 Mon Sep 17 00:00:00 2001 From: Daniel Kift Date: Wed, 30 Sep 2026 22:27:22 +0100 Subject: [PATCH 1/4] Add web Playwright harness and checkout happy path --- .ci/changed-file-filters.yml | 2 + .github/workflows/web.yml | 20 ++++++ dev.yml | 28 ++++++++- e2e/README.md | 4 ++ e2e/web/.gitignore | 3 + e2e/web/README.md | 46 ++++++++++++++ e2e/web/fixtures/host.html | 23 +++++++ e2e/web/fixtures/synthetic-checkout.html | 77 +++++++++++++++++++++++ e2e/web/package.json | 23 +++++++ e2e/web/playwright.config.ts | 27 ++++++++ e2e/web/server.mjs | 33 ++++++++++ e2e/web/support/fixtures.ts | 44 +++++++++++++ e2e/web/tests/checkout.spec.ts | 77 +++++++++++++++++++++++ e2e/web/tsconfig.json | 13 ++++ platforms/web/package.json | 8 +-- platforms/web/pnpm-lock.yaml | 46 ++++++++++++++ platforms/web/pnpm-workspace.yaml | 1 + scripts/test/changed_file_filters_test.rb | 8 +++ 18 files changed, 477 insertions(+), 6 deletions(-) create mode 100644 e2e/web/.gitignore create mode 100644 e2e/web/README.md create mode 100644 e2e/web/fixtures/host.html create mode 100644 e2e/web/fixtures/synthetic-checkout.html create mode 100644 e2e/web/package.json create mode 100644 e2e/web/playwright.config.ts create mode 100644 e2e/web/server.mjs create mode 100644 e2e/web/support/fixtures.ts create mode 100644 e2e/web/tests/checkout.spec.ts create mode 100644 e2e/web/tsconfig.json diff --git a/.ci/changed-file-filters.yml b/.ci/changed-file-filters.yml index 6d26256c9..f40db0b3f 100644 --- a/.ci/changed-file-filters.yml +++ b/.ci/changed-file-filters.yml @@ -16,6 +16,7 @@ reactNative: - *exclude-docs-directory web: - 'platforms/web/**' + - 'e2e/web/**' - 'telemetry/languages/typescript/**' - 'telemetry/contract/**' - *exclude-markdown @@ -53,6 +54,7 @@ packageSwift: - 'Package.resolved' e2e: - 'e2e/**' + - '!e2e/web/**' - *exclude-markdown - *exclude-docs-directory ciFilters: diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 30da826c4..a18a2c27e 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -44,6 +44,26 @@ jobs: - name: Build run: pnpm build + - name: Typecheck web E2E tests + run: pnpm --dir ../../e2e/web typecheck + + - name: Install Playwright Chromium + run: pnpm --dir ../../e2e/web exec playwright install --with-deps chromium + + - name: Web browser happy path + run: pnpm --dir ../../e2e/web test + + - name: Upload browser test report and failure traces + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: web-playwright + path: | + e2e/web/playwright-report/ + e2e/web/test-results/ + if-no-files-found: ignore + retention-days: 7 + - name: Verify package (publint) run: pnpm verify diff --git a/dev.yml b/dev.yml index 4694d9899..d5adac775 100644 --- a/dev.yml +++ b/dev.yml @@ -968,12 +968,12 @@ commands: pnpm run format check: - desc: Run Web lint, unit tests, build, package verification, and sample build checks + desc: Run Web lint, unit tests, browser tests, package verification, and sample build checks run: | set -e /opt/dev/bin/dev web lint /opt/dev/bin/dev web test - /opt/dev/bin/dev web build + /opt/dev/bin/dev web e2e /opt/dev/bin/dev web verify /opt/dev/bin/dev web sample build @@ -1001,6 +1001,30 @@ commands: desc: Run Web package verification run: cd platforms/web && pnpm verify + e2e: + desc: Build the web package and run Playwright in Chromium + long_desc: | + Run `dev web e2e install` once to install Chromium after `dev up`. + Tests use synthetic checkout fixtures and require no storefront config. + Extra arguments are forwarded to Playwright, for example: + + dev web e2e --headed + dev web e2e --ui + syntax: + optional: "[playwright test args]" + run: | + set -e + /opt/dev/bin/dev web build + pnpm --dir e2e/web typecheck + pnpm --dir e2e/web test "$@" + subcommands: + install: + desc: Install Playwright Chromium and its system dependencies + run: pnpm --dir e2e/web exec playwright install --with-deps chromium + report: + desc: Open the last Playwright HTML report + run: pnpm --dir e2e/web report + snapshot: desc: Manage snapshots for packaged contents run: | diff --git a/e2e/README.md b/e2e/README.md index 02add4bbb..37677fc16 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,5 +1,9 @@ # Checkout Kit End-to-End Tests +The web platform has a [Playwright browser suite](web/README.md) against the built +package with synthetic checkout fixtures. Run it with `dev web e2e`; it needs no +storefront configuration. The sections below describe the native Maestro suites. + This directory contains Maestro end-to-end flows and configuration for Checkout Kit sample apps. Two complementary setups live here: diff --git a/e2e/web/.gitignore b/e2e/web/.gitignore new file mode 100644 index 000000000..a4e7d322d --- /dev/null +++ b/e2e/web/.gitignore @@ -0,0 +1,3 @@ +test-results/ +playwright-report/ +playwright/.cache/ diff --git a/e2e/web/README.md b/e2e/web/README.md new file mode 100644 index 000000000..1de713229 --- /dev/null +++ b/e2e/web/README.md @@ -0,0 +1,46 @@ +# Web browser tests + +Playwright loads the built `platforms/web/dist/index.js` in Chromium and exercises +the real custom element, popup, and cross-origin `postMessage` exchange. A routed +HTTPS checkout fixture supplies synthetic protocol messages; telemetry is disabled +and unexpected external requests are blocked and fail the test. No storefront, +tokens, or `.env` values are needed to run the suite. + +## Run + +From the repository root: + +```sh +dev up +dev web e2e install # Install Chromium once, and again after Playwright updates +dev web e2e # Build, typecheck the tests, then run Chromium +dev web e2e --headed +dev web e2e --ui +dev web e2e report +``` + +The package is a member of the web pnpm workspace and uses +`platforms/web/pnpm-lock.yaml`. For a web-only setup, install dependencies with +`pnpm --dir platforms/web install --frozen-lockfile`, then install Chromium as above. +Once the package is built, `pnpm --dir e2e/web test` runs without rebuilding. +`dev web check` also includes the browser suite; `dev web format` formats its code. + +The fixture server binds to `127.0.0.1:4321`. Set `WEB_E2E_PORT` to use another port. +Each run starts its own server and each test gets an isolated browser context. +Failures retain traces and screenshots in `test-results/`; the HTML report is in +`playwright-report/`. Web CI uploads both as the `web-playwright` artifact. + +## Scope and layout + +The first test covers opening checkout, the `ec.ready` handshake, public `start` +and `complete` events and snapshots, and host-driven closing. It tests the built +SDK's browser integration; it does not place a real order or exercise checkout-web. + +- `server.mjs` serves the host page and built bundle. +- `fixtures/` contains the host page and synthetic checkout. +- `support/fixtures.ts` installs checkout routing and the network guard. +- `tests/checkout.spec.ts` drives the happy path through browser interactions. + +The harness builds on Mark Murray's [original Playwright PR #418](https://github.com/Shopify/checkout-kit/pull/418), +updated for the current public lifecycle API and web workspace. Additional +presentation, error, origin-validation, and cross-browser cases can build on it. diff --git a/e2e/web/fixtures/host.html b/e2e/web/fixtures/host.html new file mode 100644 index 000000000..edd98ef67 --- /dev/null +++ b/e2e/web/fixtures/host.html @@ -0,0 +1,23 @@ + + + + + Checkout Kit browser test + + + + + + + diff --git a/e2e/web/fixtures/synthetic-checkout.html b/e2e/web/fixtures/synthetic-checkout.html new file mode 100644 index 000000000..c01bd4024 --- /dev/null +++ b/e2e/web/fixtures/synthetic-checkout.html @@ -0,0 +1,77 @@ + + + + + Synthetic checkout + + +

Waiting for handshake

+ + + + + diff --git a/e2e/web/package.json b/e2e/web/package.json new file mode 100644 index 000000000..965772cd7 --- /dev/null +++ b/e2e/web/package.json @@ -0,0 +1,23 @@ +{ + "name": "@shopify/checkout-kit-web-e2e", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "test": "playwright test", + "typecheck": "tsc --noEmit", + "lint": "oxlint --config ../../platforms/web/.oxlintrc.json --report-unused-disable-directives --max-warnings 0 support tests playwright.config.ts server.mjs", + "lint:fix": "pnpm lint --fix", + "format": "oxfmt support tests fixtures playwright.config.ts server.mjs package.json tsconfig.json", + "format:check": "pnpm format --check", + "report": "playwright show-report" + }, + "devDependencies": { + "@playwright/test": "^1.61.1", + "@shopify/checkout-kit": "workspace:*", + "@types/node": "^22.10.0", + "oxfmt": "^0.47.0", + "oxlint": "^1.62.0", + "typescript": "5.9.3" + } +} diff --git a/e2e/web/playwright.config.ts b/e2e/web/playwright.config.ts new file mode 100644 index 000000000..0f81d7452 --- /dev/null +++ b/e2e/web/playwright.config.ts @@ -0,0 +1,27 @@ +import { defineConfig, devices } from "@playwright/test"; + +const port = Number(process.env.WEB_E2E_PORT ?? 4321); +const baseURL = `http://127.0.0.1:${port}`; + +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + forbidOnly: Boolean(process.env.CI), + retries: process.env.CI ? 1 : 0, + reporter: [["list"], ["html", { open: "never" }]], + use: { + baseURL, + serviceWorkers: "block", + trace: "retain-on-failure", + screenshot: "only-on-failure", + }, + projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }], + webServer: { + command: "node server.mjs", + url: baseURL, + // Always start our own server so a stale build or another process cannot + // accidentally satisfy the readiness check. + reuseExistingServer: false, + env: { WEB_E2E_PORT: String(port) }, + }, +}); diff --git a/e2e/web/server.mjs b/e2e/web/server.mjs new file mode 100644 index 000000000..cf4ab88ae --- /dev/null +++ b/e2e/web/server.mjs @@ -0,0 +1,33 @@ +import { createServer } from "node:http"; +import { access, readFile } from "node:fs/promises"; + +const bundle = new URL("../../platforms/web/dist/index.js", import.meta.url); +await access(bundle).catch(() => { + throw new Error("Built web component not found. Run `dev web build` first."); +}); + +const files = new Map([ + ["/", [new URL("./fixtures/host.html", import.meta.url), "text/html"]], + ["/dist/index.js", [bundle, "text/javascript"]], +]); + +createServer(async (request, response) => { + const path = new URL(request.url ?? "/", "http://localhost").pathname; + const file = files.get(path); + if (!file) { + response.writeHead(404).end("Not found"); + return; + } + + try { + const [url, contentType] = file; + const body = await readFile(url); + response.writeHead(200, { + "content-type": `${contentType}; charset=utf-8`, + "cache-control": "no-store", + }); + response.end(body); + } catch { + response.writeHead(500).end("Could not read fixture"); + } +}).listen(Number(process.env.WEB_E2E_PORT ?? 4321), "127.0.0.1"); diff --git a/e2e/web/support/fixtures.ts b/e2e/web/support/fixtures.ts new file mode 100644 index 000000000..50bb072e6 --- /dev/null +++ b/e2e/web/support/fixtures.ts @@ -0,0 +1,44 @@ +import { fileURLToPath } from "node:url"; +import { test as base, expect } from "@playwright/test"; +import type { ShopifyCheckoutEventMap } from "@shopify/checkout-kit"; + +type CheckoutEventRecord = { + [K in keyof ShopifyCheckoutEventMap]: { + type: K; + detail: ShopifyCheckoutEventMap[K]["detail"] | null; + }; +}[keyof ShopifyCheckoutEventMap]; + +declare global { + interface Window { + checkoutEvents: CheckoutEventRecord[]; + } +} + +export const checkoutOrigin = "https://checkout.example.test"; +const syntheticCheckout = fileURLToPath( + new URL("../fixtures/synthetic-checkout.html", import.meta.url), +); + +export const test = base.extend<{ networkGuard: void }>({ + networkGuard: [ + async ({ context, baseURL }, use) => { + const unexpectedRequests: string[] = []; + await context.route("**/*", async (route) => { + const url = new URL(route.request().url()); + if (url.origin === baseURL) return route.continue(); + if (url.origin === checkoutOrigin) { + return route.fulfill({ path: syntheticCheckout, contentType: "text/html" }); + } + unexpectedRequests.push(url.origin); + await route.abort(); + }); + + await use(); + expect(unexpectedRequests, "All requests should stay within the local fixtures").toEqual([]); + }, + { auto: true }, + ], +}); + +export { expect }; diff --git a/e2e/web/tests/checkout.spec.ts b/e2e/web/tests/checkout.spec.ts new file mode 100644 index 000000000..5dff3812f --- /dev/null +++ b/e2e/web/tests/checkout.spec.ts @@ -0,0 +1,77 @@ +import type { ShopifyCheckout } from "@shopify/checkout-kit"; +import { checkoutOrigin, expect, test } from "../support/fixtures"; + +test("opens checkout, completes the handshake and purchase, then closes", async ({ page }) => { + await page.goto("/"); + await page.evaluate(() => customElements.whenDefined("shopify-checkout")); + await page.locator("shopify-checkout").evaluate((element, origin) => { + const src = new URL("/checkout/checkout_test_1", origin); + src.searchParams.set("host_origin", location.origin); + (element as ShopifyCheckout).src = src.href; + }, checkoutOrigin); + + const popupPromise = page.waitForEvent("popup"); + await page.getByRole("button", { name: "Buy", exact: true }).click(); + const popup = await popupPromise; + + await expect(page.locator("#overlay")).toBeVisible(); + await expect(popup.locator("#status")).toHaveText("Ready"); + await popup.getByRole("button", { name: "Start checkout" }).click(); + await expect + .poll(() => page.evaluate(() => window.checkoutEvents)) + .toEqual([ + { + type: "start", + detail: { + checkout: { + id: "checkout_test_1", + currency: "USD", + status: "incomplete", + lineItems: [ + { + id: "li_1", + item: { id: "variant_1", title: "Test Product", price: 2000 }, + quantity: 1, + totals: [{ type: "total", amount: 2000 }], + }, + ], + totals: [{ type: "total", amount: 2000 }], + links: [], + }, + }, + }, + ]); + + await popup.getByRole("button", { name: "Complete checkout" }).click(); + await expect + .poll(() => page.evaluate(() => window.checkoutEvents.map(({ type }) => type))) + .toEqual(["start", "complete"]); + + const complete = await page.evaluate(() => + window.checkoutEvents.find((event) => event.type === "complete"), + ); + expect(complete?.detail?.checkout).toMatchObject({ + id: "checkout_test_1", + status: "completed", + order: { + id: "order_test_1", + permalinkUrl: `${checkoutOrigin}/orders/order_test_1`, + }, + }); + expect(complete?.detail?.checkout).not.toHaveProperty("ucp"); + expect( + await page + .locator("shopify-checkout") + .evaluate((element) => (element as ShopifyCheckout).checkout), + ).toEqual(complete?.detail?.checkout); + + // Completion leaves presentation under the host's control. + await page + .locator("shopify-checkout") + .evaluate((element) => (element as ShopifyCheckout).close()); + await expect.poll(() => popup.isClosed()).toBe(true); + await expect(page.locator("#overlay")).not.toBeVisible(); + await expect + .poll(() => page.evaluate(() => window.checkoutEvents.map(({ type }) => type))) + .toEqual(["start", "complete", "close"]); +}); diff --git a/e2e/web/tsconfig.json b/e2e/web/tsconfig.json new file mode 100644 index 000000000..10894fe00 --- /dev/null +++ b/e2e/web/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "types": ["node"], + "strict": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["tests", "support", "playwright.config.ts"] +} diff --git a/platforms/web/package.json b/platforms/web/package.json index 0251d45fc..04221d065 100644 --- a/platforms/web/package.json +++ b/platforms/web/package.json @@ -58,10 +58,10 @@ "test": "vitest run --coverage", "test:watch": "vitest", "lint": "pnpm run typecheck && pnpm run sample:typecheck && pnpm run lint:js && pnpm run format:check", - "lint:js": "oxlint --report-unused-disable-directives --max-warnings 0 src sample", - "lint:js:fix": "oxlint --fix src sample", - "format": "oxfmt src sample '!sample/dist/**'", - "format:check": "oxfmt --check src sample '!sample/dist/**'", + "lint:js": "oxlint --report-unused-disable-directives --max-warnings 0 src sample && pnpm --dir ../../e2e/web lint", + "lint:js:fix": "oxlint --fix src sample && pnpm --dir ../../e2e/web lint:fix", + "format": "oxfmt src sample '!sample/dist/**' && pnpm --dir ../../e2e/web format", + "format:check": "oxfmt --check src sample '!sample/dist/**' && pnpm --dir ../../e2e/web format:check", "typecheck": "tsc --noEmit", "sample": "vite --config sample/vite.config.ts", "sample:build": "vite build --config sample/vite.config.ts", diff --git a/platforms/web/pnpm-lock.yaml b/platforms/web/pnpm-lock.yaml index 6e662ca87..c6ca1a28a 100644 --- a/platforms/web/pnpm-lock.yaml +++ b/platforms/web/pnpm-lock.yaml @@ -48,6 +48,27 @@ importers: specifier: ^4.1.0 version: 4.1.11(@types/node@22.19.18)(@vitest/coverage-v8@4.1.11)(happy-dom@20.8.9)(vite@8.0.16(@types/node@22.19.18)(esbuild@0.28.1)) + ../../e2e/web: + devDependencies: + '@playwright/test': + specifier: ^1.61.1 + version: 1.63.0 + '@shopify/checkout-kit': + specifier: workspace:* + version: link:../../platforms/web + '@types/node': + specifier: ^22.10.0 + version: 22.19.18 + oxfmt: + specifier: ^0.47.0 + version: 0.47.0 + oxlint: + specifier: ^1.62.0 + version: 1.63.0 + typescript: + specifier: 5.9.3 + version: 5.9.3 + ../../protocol/languages/typescript: dependencies: '@babel/runtime': @@ -567,6 +588,11 @@ packages: cpu: [x64] os: [win32] + '@playwright/test@1.63.0': + resolution: {integrity: sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==, tarball: https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz} + engines: {node: '>=20'} + hasBin: true + '@publint/pack@0.1.7': resolution: {integrity: sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==} engines: {node: '>=18'} @@ -1650,6 +1676,16 @@ packages: pkg-types@2.3.1: resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + playwright-core@1.63.0: + resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==, tarball: https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz} + engines: {node: '>=20'} + hasBin: true + + playwright@1.63.0: + resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==, tarball: https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz} + engines: {node: '>=20'} + hasBin: true + postcss@8.5.15: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} @@ -2285,6 +2321,10 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.63.0': optional: true + '@playwright/test@1.63.0': + dependencies: + playwright: 1.63.0 + '@publint/pack@0.1.7': dependencies: tinyexec: 1.3.0 @@ -3186,6 +3226,12 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + playwright-core@1.63.0: {} + + playwright@1.63.0: + dependencies: + playwright-core: 1.63.0 + postcss@8.5.15: dependencies: nanoid: 3.3.12 diff --git a/platforms/web/pnpm-workspace.yaml b/platforms/web/pnpm-workspace.yaml index 81b60d90a..83e98edb2 100644 --- a/platforms/web/pnpm-workspace.yaml +++ b/platforms/web/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: + - '../../e2e/web' - '../../protocol/languages/typescript' - '../../telemetry/languages/typescript' diff --git a/scripts/test/changed_file_filters_test.rb b/scripts/test/changed_file_filters_test.rb index aa83473fc..0aeee1872 100644 --- a/scripts/test/changed_file_filters_test.rb +++ b/scripts/test/changed_file_filters_test.rb @@ -66,6 +66,14 @@ def test_matches_shared_protocol_inputs_and_generation_tooling refute @filters.match?("protocolTypescript", ["protocol/package.json"]) end + def test_web_browser_tests_trigger_web_without_triggering_native_e2e + paths = ["e2e/web/tests/checkout.spec.ts"] + assert @filters.match?("web", paths) + refute @filters.match?("e2e", paths) + refute @filters.match?("web", ["e2e/web/README.md"]) + assert @filters.match?("e2e", ["e2e/tests/shared/checkout-guest.yaml"]) + end + def test_excludes_markdown_at_top_level_and_nested refute @filters.match?("android", ["platforms/android/README.md"]) refute @filters.match?("web", ["platforms/web/guides/nested/CHANGELOG.md"]) From 638d5b23d9e1b92c0b7063e50db3e67c1f0525a9 Mon Sep 17 00:00:00 2001 From: Daniel Kift Date: Wed, 30 Sep 2026 22:44:42 +0100 Subject: [PATCH 2/4] Provision Playwright Chromium during dev up --- dev.yml | 11 +++++++++-- e2e/web/README.md | 9 ++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/dev.yml b/dev.yml index d5adac775..81fc5aaea 100644 --- a/dev.yml +++ b/dev.yml @@ -27,6 +27,12 @@ up: - protocol - platforms/react-native - platforms/web + - custom: + name: Install Playwright Chromium for web E2E + # Playwright checks its own browser revisions; current installs are a no-op. + met?: pnpm --dir e2e/web exec playwright install chromium + # Do not hide installation failures behind a successful meet command. + meet: "false" - jdk: version: "17" distribution: openjdk @@ -1004,7 +1010,8 @@ commands: e2e: desc: Build the web package and run Playwright in Chromium long_desc: | - Run `dev web e2e install` once to install Chromium after `dev up`. + `dev up` installs Chromium for the current Playwright version. + Use `dev web e2e install` for targeted setup or recovery. Tests use synthetic checkout fixtures and require no storefront config. Extra arguments are forwarded to Playwright, for example: @@ -1019,7 +1026,7 @@ commands: pnpm --dir e2e/web test "$@" subcommands: install: - desc: Install Playwright Chromium and its system dependencies + desc: Install or repair Playwright Chromium and its system dependencies run: pnpm --dir e2e/web exec playwright install --with-deps chromium report: desc: Open the last Playwright HTML report diff --git a/e2e/web/README.md b/e2e/web/README.md index 1de713229..36640bd3c 100644 --- a/e2e/web/README.md +++ b/e2e/web/README.md @@ -11,8 +11,7 @@ tokens, or `.env` values are needed to run the suite. From the repository root: ```sh -dev up -dev web e2e install # Install Chromium once, and again after Playwright updates +dev up # Install dependencies and the matching Chromium browsers dev web e2e # Build, typecheck the tests, then run Chromium dev web e2e --headed dev web e2e --ui @@ -21,7 +20,11 @@ dev web e2e report The package is a member of the web pnpm workspace and uses `platforms/web/pnpm-lock.yaml`. For a web-only setup, install dependencies with -`pnpm --dir platforms/web install --frozen-lockfile`, then install Chromium as above. +`pnpm --dir platforms/web install --frozen-lockfile`, then run `dev web e2e install`. +That command also provides targeted browser setup or recovery, including system +dependencies on Linux. After Playwright updates, rerun `dev up` or +`dev web e2e install` to install the matching browsers. Existing browser installs +are reused when they match the installed Playwright version. Once the package is built, `pnpm --dir e2e/web test` runs without rebuilding. `dev web check` also includes the browser suite; `dev web format` formats its code. From 99c2a97748d4879a39473f899667aca7d4eacc45 Mon Sep 17 00:00:00 2001 From: Daniel Kift Date: Wed, 30 Sep 2026 22:56:40 +0100 Subject: [PATCH 3/4] Move browser harness into the web platform test workspace --- .ci/changed-file-filters.yml | 2 - .github/workflows/web.yml | 10 ++--- dev.yml | 10 ++--- e2e/README.md | 3 +- platforms/web/package.json | 8 ++-- platforms/web/pnpm-lock.yaml | 42 +++++++++---------- platforms/web/pnpm-workspace.yaml | 2 +- .../web => platforms/web/test/e2e}/.gitignore | 0 {e2e/web => platforms/web/test/e2e}/README.md | 2 +- .../web/test/e2e}/fixtures/host.html | 0 .../e2e}/fixtures/synthetic-checkout.html | 0 .../web/test/e2e}/package.json | 2 +- .../web/test/e2e}/playwright.config.ts | 0 .../web => platforms/web/test/e2e}/server.mjs | 2 +- .../web/test/e2e}/support/fixtures.ts | 0 .../web/test/e2e}/tests/checkout.spec.ts | 0 .../web/test/e2e}/tsconfig.json | 0 scripts/test/changed_file_filters_test.rb | 4 +- 18 files changed, 43 insertions(+), 44 deletions(-) rename {e2e/web => platforms/web/test/e2e}/.gitignore (100%) rename {e2e/web => platforms/web/test/e2e}/README.md (96%) rename {e2e/web => platforms/web/test/e2e}/fixtures/host.html (100%) rename {e2e/web => platforms/web/test/e2e}/fixtures/synthetic-checkout.html (100%) rename {e2e/web => platforms/web/test/e2e}/package.json (79%) rename {e2e/web => platforms/web/test/e2e}/playwright.config.ts (100%) rename {e2e/web => platforms/web/test/e2e}/server.mjs (92%) rename {e2e/web => platforms/web/test/e2e}/support/fixtures.ts (100%) rename {e2e/web => platforms/web/test/e2e}/tests/checkout.spec.ts (100%) rename {e2e/web => platforms/web/test/e2e}/tsconfig.json (100%) diff --git a/.ci/changed-file-filters.yml b/.ci/changed-file-filters.yml index f40db0b3f..6d26256c9 100644 --- a/.ci/changed-file-filters.yml +++ b/.ci/changed-file-filters.yml @@ -16,7 +16,6 @@ reactNative: - *exclude-docs-directory web: - 'platforms/web/**' - - 'e2e/web/**' - 'telemetry/languages/typescript/**' - 'telemetry/contract/**' - *exclude-markdown @@ -54,7 +53,6 @@ packageSwift: - 'Package.resolved' e2e: - 'e2e/**' - - '!e2e/web/**' - *exclude-markdown - *exclude-docs-directory ciFilters: diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index a18a2c27e..2921c0654 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -45,13 +45,13 @@ jobs: run: pnpm build - name: Typecheck web E2E tests - run: pnpm --dir ../../e2e/web typecheck + run: pnpm --dir test/e2e typecheck - name: Install Playwright Chromium - run: pnpm --dir ../../e2e/web exec playwright install --with-deps chromium + run: pnpm --dir test/e2e exec playwright install --with-deps chromium - name: Web browser happy path - run: pnpm --dir ../../e2e/web test + run: pnpm --dir test/e2e test - name: Upload browser test report and failure traces if: ${{ !cancelled() }} @@ -59,8 +59,8 @@ jobs: with: name: web-playwright path: | - e2e/web/playwright-report/ - e2e/web/test-results/ + platforms/web/test/e2e/playwright-report/ + platforms/web/test/e2e/test-results/ if-no-files-found: ignore retention-days: 7 diff --git a/dev.yml b/dev.yml index 81fc5aaea..6f62f205e 100644 --- a/dev.yml +++ b/dev.yml @@ -30,7 +30,7 @@ up: - custom: name: Install Playwright Chromium for web E2E # Playwright checks its own browser revisions; current installs are a no-op. - met?: pnpm --dir e2e/web exec playwright install chromium + met?: pnpm --dir platforms/web/test/e2e exec playwright install chromium # Do not hide installation failures behind a successful meet command. meet: "false" - jdk: @@ -1022,15 +1022,15 @@ commands: run: | set -e /opt/dev/bin/dev web build - pnpm --dir e2e/web typecheck - pnpm --dir e2e/web test "$@" + pnpm --dir platforms/web/test/e2e typecheck + pnpm --dir platforms/web/test/e2e test "$@" subcommands: install: desc: Install or repair Playwright Chromium and its system dependencies - run: pnpm --dir e2e/web exec playwright install --with-deps chromium + run: pnpm --dir platforms/web/test/e2e exec playwright install --with-deps chromium report: desc: Open the last Playwright HTML report - run: pnpm --dir e2e/web report + run: pnpm --dir platforms/web/test/e2e report snapshot: desc: Manage snapshots for packaged contents diff --git a/e2e/README.md b/e2e/README.md index 37677fc16..f6e95c987 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -1,6 +1,7 @@ # Checkout Kit End-to-End Tests -The web platform has a [Playwright browser suite](web/README.md) against the built +The web platform has a +[Playwright browser suite](../platforms/web/test/e2e/README.md) against the built package with synthetic checkout fixtures. Run it with `dev web e2e`; it needs no storefront configuration. The sections below describe the native Maestro suites. diff --git a/platforms/web/package.json b/platforms/web/package.json index 04221d065..fe0c3e460 100644 --- a/platforms/web/package.json +++ b/platforms/web/package.json @@ -58,10 +58,10 @@ "test": "vitest run --coverage", "test:watch": "vitest", "lint": "pnpm run typecheck && pnpm run sample:typecheck && pnpm run lint:js && pnpm run format:check", - "lint:js": "oxlint --report-unused-disable-directives --max-warnings 0 src sample && pnpm --dir ../../e2e/web lint", - "lint:js:fix": "oxlint --fix src sample && pnpm --dir ../../e2e/web lint:fix", - "format": "oxfmt src sample '!sample/dist/**' && pnpm --dir ../../e2e/web format", - "format:check": "oxfmt --check src sample '!sample/dist/**' && pnpm --dir ../../e2e/web format:check", + "lint:js": "oxlint --report-unused-disable-directives --max-warnings 0 src sample && pnpm --dir test/e2e lint", + "lint:js:fix": "oxlint --fix src sample && pnpm --dir test/e2e lint:fix", + "format": "oxfmt src sample '!sample/dist/**' && pnpm --dir test/e2e format", + "format:check": "oxfmt --check src sample '!sample/dist/**' && pnpm --dir test/e2e format:check", "typecheck": "tsc --noEmit", "sample": "vite --config sample/vite.config.ts", "sample:build": "vite build --config sample/vite.config.ts", diff --git a/platforms/web/pnpm-lock.yaml b/platforms/web/pnpm-lock.yaml index c6ca1a28a..ca445c065 100644 --- a/platforms/web/pnpm-lock.yaml +++ b/platforms/web/pnpm-lock.yaml @@ -48,27 +48,6 @@ importers: specifier: ^4.1.0 version: 4.1.11(@types/node@22.19.18)(@vitest/coverage-v8@4.1.11)(happy-dom@20.8.9)(vite@8.0.16(@types/node@22.19.18)(esbuild@0.28.1)) - ../../e2e/web: - devDependencies: - '@playwright/test': - specifier: ^1.61.1 - version: 1.63.0 - '@shopify/checkout-kit': - specifier: workspace:* - version: link:../../platforms/web - '@types/node': - specifier: ^22.10.0 - version: 22.19.18 - oxfmt: - specifier: ^0.47.0 - version: 0.47.0 - oxlint: - specifier: ^1.62.0 - version: 1.63.0 - typescript: - specifier: 5.9.3 - version: 5.9.3 - ../../protocol/languages/typescript: dependencies: '@babel/runtime': @@ -95,6 +74,27 @@ importers: specifier: ^4.1.0 version: 4.1.11(@types/node@22.19.18)(@vitest/coverage-v8@4.1.11)(happy-dom@20.8.9)(vite@8.2.2(@types/node@22.19.18)(esbuild@0.28.1)) + test/e2e: + devDependencies: + '@playwright/test': + specifier: ^1.61.1 + version: 1.63.0 + '@shopify/checkout-kit': + specifier: workspace:* + version: link:../.. + '@types/node': + specifier: ^22.10.0 + version: 22.19.18 + oxfmt: + specifier: ^0.47.0 + version: 0.47.0 + oxlint: + specifier: ^1.62.0 + version: 1.63.0 + typescript: + specifier: 5.9.3 + version: 5.9.3 + packages: '@babel/helper-string-parser@7.29.7': diff --git a/platforms/web/pnpm-workspace.yaml b/platforms/web/pnpm-workspace.yaml index 83e98edb2..6c63c275c 100644 --- a/platforms/web/pnpm-workspace.yaml +++ b/platforms/web/pnpm-workspace.yaml @@ -1,4 +1,4 @@ packages: - - '../../e2e/web' + - 'test/e2e' - '../../protocol/languages/typescript' - '../../telemetry/languages/typescript' diff --git a/e2e/web/.gitignore b/platforms/web/test/e2e/.gitignore similarity index 100% rename from e2e/web/.gitignore rename to platforms/web/test/e2e/.gitignore diff --git a/e2e/web/README.md b/platforms/web/test/e2e/README.md similarity index 96% rename from e2e/web/README.md rename to platforms/web/test/e2e/README.md index 36640bd3c..5b3ed80c6 100644 --- a/e2e/web/README.md +++ b/platforms/web/test/e2e/README.md @@ -25,7 +25,7 @@ That command also provides targeted browser setup or recovery, including system dependencies on Linux. After Playwright updates, rerun `dev up` or `dev web e2e install` to install the matching browsers. Existing browser installs are reused when they match the installed Playwright version. -Once the package is built, `pnpm --dir e2e/web test` runs without rebuilding. +Once the package is built, `pnpm --dir platforms/web/test/e2e test` runs without rebuilding. `dev web check` also includes the browser suite; `dev web format` formats its code. The fixture server binds to `127.0.0.1:4321`. Set `WEB_E2E_PORT` to use another port. diff --git a/e2e/web/fixtures/host.html b/platforms/web/test/e2e/fixtures/host.html similarity index 100% rename from e2e/web/fixtures/host.html rename to platforms/web/test/e2e/fixtures/host.html diff --git a/e2e/web/fixtures/synthetic-checkout.html b/platforms/web/test/e2e/fixtures/synthetic-checkout.html similarity index 100% rename from e2e/web/fixtures/synthetic-checkout.html rename to platforms/web/test/e2e/fixtures/synthetic-checkout.html diff --git a/e2e/web/package.json b/platforms/web/test/e2e/package.json similarity index 79% rename from e2e/web/package.json rename to platforms/web/test/e2e/package.json index 965772cd7..e5e7f03a4 100644 --- a/e2e/web/package.json +++ b/platforms/web/test/e2e/package.json @@ -6,7 +6,7 @@ "scripts": { "test": "playwright test", "typecheck": "tsc --noEmit", - "lint": "oxlint --config ../../platforms/web/.oxlintrc.json --report-unused-disable-directives --max-warnings 0 support tests playwright.config.ts server.mjs", + "lint": "oxlint --config ../../.oxlintrc.json --report-unused-disable-directives --max-warnings 0 support tests playwright.config.ts server.mjs", "lint:fix": "pnpm lint --fix", "format": "oxfmt support tests fixtures playwright.config.ts server.mjs package.json tsconfig.json", "format:check": "pnpm format --check", diff --git a/e2e/web/playwright.config.ts b/platforms/web/test/e2e/playwright.config.ts similarity index 100% rename from e2e/web/playwright.config.ts rename to platforms/web/test/e2e/playwright.config.ts diff --git a/e2e/web/server.mjs b/platforms/web/test/e2e/server.mjs similarity index 92% rename from e2e/web/server.mjs rename to platforms/web/test/e2e/server.mjs index cf4ab88ae..638035cc6 100644 --- a/e2e/web/server.mjs +++ b/platforms/web/test/e2e/server.mjs @@ -1,7 +1,7 @@ import { createServer } from "node:http"; import { access, readFile } from "node:fs/promises"; -const bundle = new URL("../../platforms/web/dist/index.js", import.meta.url); +const bundle = new URL("../../dist/index.js", import.meta.url); await access(bundle).catch(() => { throw new Error("Built web component not found. Run `dev web build` first."); }); diff --git a/e2e/web/support/fixtures.ts b/platforms/web/test/e2e/support/fixtures.ts similarity index 100% rename from e2e/web/support/fixtures.ts rename to platforms/web/test/e2e/support/fixtures.ts diff --git a/e2e/web/tests/checkout.spec.ts b/platforms/web/test/e2e/tests/checkout.spec.ts similarity index 100% rename from e2e/web/tests/checkout.spec.ts rename to platforms/web/test/e2e/tests/checkout.spec.ts diff --git a/e2e/web/tsconfig.json b/platforms/web/test/e2e/tsconfig.json similarity index 100% rename from e2e/web/tsconfig.json rename to platforms/web/test/e2e/tsconfig.json diff --git a/scripts/test/changed_file_filters_test.rb b/scripts/test/changed_file_filters_test.rb index 0aeee1872..1d95b462a 100644 --- a/scripts/test/changed_file_filters_test.rb +++ b/scripts/test/changed_file_filters_test.rb @@ -67,10 +67,10 @@ def test_matches_shared_protocol_inputs_and_generation_tooling end def test_web_browser_tests_trigger_web_without_triggering_native_e2e - paths = ["e2e/web/tests/checkout.spec.ts"] + paths = ["platforms/web/test/e2e/tests/checkout.spec.ts"] assert @filters.match?("web", paths) refute @filters.match?("e2e", paths) - refute @filters.match?("web", ["e2e/web/README.md"]) + refute @filters.match?("web", ["platforms/web/test/e2e/README.md"]) assert @filters.match?("e2e", ["e2e/tests/shared/checkout-guest.yaml"]) end From e499d4f68a1d1b6187b969396164d20617cd42bf Mon Sep 17 00:00:00 2001 From: Daniel Kift Date: Thu, 1 Oct 2026 13:09:41 +0100 Subject: [PATCH 4/4] Remove historical context from E2E README --- platforms/web/test/e2e/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/platforms/web/test/e2e/README.md b/platforms/web/test/e2e/README.md index 5b3ed80c6..c8132cdf3 100644 --- a/platforms/web/test/e2e/README.md +++ b/platforms/web/test/e2e/README.md @@ -43,7 +43,3 @@ SDK's browser integration; it does not place a real order or exercise checkout-w - `fixtures/` contains the host page and synthetic checkout. - `support/fixtures.ts` installs checkout routing and the network guard. - `tests/checkout.spec.ts` drives the happy path through browser interactions. - -The harness builds on Mark Murray's [original Playwright PR #418](https://github.com/Shopify/checkout-kit/pull/418), -updated for the current public lifecycle API and web workspace. Additional -presentation, error, origin-validation, and cross-browser cases can build on it.