From 64823f5d78635ada127b2d142ab0b6dfa16aa138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Tue, 23 Sep 2025 13:29:18 +0200 Subject: [PATCH 01/15] Add Safari E2E tests to CI --- .github/workflows/canister-tests.yml | 12 +++++++++--- playwright.config.ts | 28 ++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/canister-tests.yml b/.github/workflows/canister-tests.yml index 055d2e783b..1ea6d327cb 100644 --- a/.github/workflows/canister-tests.yml +++ b/.github/workflows/canister-tests.yml @@ -582,6 +582,7 @@ jobs: needs: [cached-build, test-app-build] strategy: matrix: + browser: ["chrome", "safari"] device: ["desktop", "mobile"] shard: ["1_3", "2_3", "3_3"] # Make sure that one failing test does not cancel all other matrix jobs @@ -589,7 +590,7 @@ jobs: env: # Suffix used for tagging artifacts - artifact_suffix: next-${{ matrix.device }}-${{ matrix.shard }} + artifact_suffix: next-${{ matrix.browser }}-${{ matrix.device }}-${{ matrix.shard }} # OpenID configuration for provider in /src/test_openid_provider openid_name: Test OpenID openid_logo: | @@ -615,7 +616,12 @@ jobs: run: npm ci --no-audit --no-fund - name: Install Playwright Browsers - run: npx playwright install chromium + run: | + if [ "${{ matrix.browser }}" = "chrome" ]; then + npx playwright install chromium + else + npx playwright install webkit + fi - uses: dfinity/setup-dfx@e50c04f104ee4285ec010f10609483cf41e4d365 @@ -666,7 +672,7 @@ jobs: echo "dev_server_pid=$dev_server_pid" >> "$GITHUB_OUTPUT" - run: | - npx playwright test --project ${{ matrix.device }} --workers 1 --shard=$(tr <<<'${{ matrix.shard }}' -s _ /) + npx playwright test --project ${{ matrix.browser }}-${{ matrix.device }} --workers 1 --shard=$(tr <<<'${{ matrix.shard }}' -s _ /) - name: Stop dfx if: ${{ always() }} diff --git a/playwright.config.ts b/playwright.config.ts index fa76e390a2..0bf5e31372 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -36,7 +36,7 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ { - name: "desktop", + name: "chrome-desktop", use: { ...devices["Desktop Chrome"], launchOptions: { @@ -48,7 +48,7 @@ export default defineConfig({ }, }, { - name: "mobile", + name: "chrome-mobile", use: { ...devices["Pixel 5"], launchOptions: { @@ -59,5 +59,29 @@ export default defineConfig({ }, }, }, + { + name: "safari-desktop", + use: { + ...devices["Desktop Safari"], + launchOptions: { + args: [ + "--ignore-certificate-errors", + "--host-resolver-rules=MAP * localhost:5173, EXCLUDE localhost", + ], + }, + }, + }, + { + name: "safari-mobile", + use: { + ...devices["iPhone 12"], + launchOptions: { + args: [ + "--ignore-certificate-errors", + "--host-resolver-rules=MAP * localhost:5173, EXCLUDE localhost", + ], + }, + }, + }, ], }); From b42c0186a4a62301489e38f593ec0e6d7fb67946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Wed, 24 Sep 2025 09:31:04 +0200 Subject: [PATCH 02/15] try to fix Safari CI --- .github/workflows/canister-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/canister-tests.yml b/.github/workflows/canister-tests.yml index 1ea6d327cb..5e5edfba25 100644 --- a/.github/workflows/canister-tests.yml +++ b/.github/workflows/canister-tests.yml @@ -618,9 +618,9 @@ jobs: - name: Install Playwright Browsers run: | if [ "${{ matrix.browser }}" = "chrome" ]; then - npx playwright install chromium + npx playwright install --with-deps chromium else - npx playwright install webkit + npx playwright install --with-deps webkit fi - uses: dfinity/setup-dfx@e50c04f104ee4285ec010f10609483cf41e4d365 From 7ad82dc957e4d7f43532165a2905df784319ceb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Wed, 24 Sep 2025 16:12:45 +0200 Subject: [PATCH 03/15] Remove ignore certificate errors safari CI --- playwright.config.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 0bf5e31372..7d71d1deb3 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -65,7 +65,6 @@ export default defineConfig({ ...devices["Desktop Safari"], launchOptions: { args: [ - "--ignore-certificate-errors", "--host-resolver-rules=MAP * localhost:5173, EXCLUDE localhost", ], }, @@ -77,7 +76,6 @@ export default defineConfig({ ...devices["iPhone 12"], launchOptions: { args: [ - "--ignore-certificate-errors", "--host-resolver-rules=MAP * localhost:5173, EXCLUDE localhost", ], }, From 5993c873e21521e8dbd3eccc4903145199098735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Thu, 25 Sep 2025 09:26:42 +0200 Subject: [PATCH 04/15] Use different rule for Safari --- playwright.config.ts | 12 +----- .../e2e-playwright/authorize/account.spec.ts | 2 +- .../authorize/alternativeOrigins.spec.ts | 2 +- .../e2e-playwright/authorize/continue.spec.ts | 2 +- .../authorize/delegationTtl.spec.ts | 2 +- .../e2e-playwright/authorize/index.spec.ts | 2 +- .../authorize/migration.spec.ts | 2 +- .../authorize/postMessages.spec.ts | 2 +- .../dashboard/addPasskeys.spec.ts | 2 +- .../e2e-playwright/dashboard/index.spec.ts | 2 +- .../dashboard/migration.spec.ts | 3 +- .../dashboard/removePasskey.spec.ts | 2 +- .../dashboard/renamePasskeys.spec.ts | 2 +- src/frontend/tests/e2e-playwright/fixtures.ts | 37 +++++++++++++++++++ .../tests/e2e-playwright/index.spec.ts | 2 +- 15 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 src/frontend/tests/e2e-playwright/fixtures.ts diff --git a/playwright.config.ts b/playwright.config.ts index 7d71d1deb3..5613ac9f26 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -30,6 +30,8 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", + /* Accept self-signed certificates when dev server runs with TLS */ + ignoreHTTPSErrors: true, }, timeout: 60000, @@ -63,22 +65,12 @@ export default defineConfig({ name: "safari-desktop", use: { ...devices["Desktop Safari"], - launchOptions: { - args: [ - "--host-resolver-rules=MAP * localhost:5173, EXCLUDE localhost", - ], - }, }, }, { name: "safari-mobile", use: { ...devices["iPhone 12"], - launchOptions: { - args: [ - "--host-resolver-rules=MAP * localhost:5173, EXCLUDE localhost", - ], - }, }, }, ], diff --git a/src/frontend/tests/e2e-playwright/authorize/account.spec.ts b/src/frontend/tests/e2e-playwright/authorize/account.spec.ts index 1899faaf94..432575ff10 100644 --- a/src/frontend/tests/e2e-playwright/authorize/account.spec.ts +++ b/src/frontend/tests/e2e-playwright/authorize/account.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import { authorize, createIdentity, dummyAuth } from "../utils"; test("Create and authorize with additional account", async ({ page }) => { diff --git a/src/frontend/tests/e2e-playwright/authorize/alternativeOrigins.spec.ts b/src/frontend/tests/e2e-playwright/authorize/alternativeOrigins.spec.ts index a907ee374b..4f31f2e891 100644 --- a/src/frontend/tests/e2e-playwright/authorize/alternativeOrigins.spec.ts +++ b/src/frontend/tests/e2e-playwright/authorize/alternativeOrigins.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "@playwright/test"; +import { test, expect } from "../fixtures"; import { dummyAuth, II_URL, diff --git a/src/frontend/tests/e2e-playwright/authorize/continue.spec.ts b/src/frontend/tests/e2e-playwright/authorize/continue.spec.ts index 72e8d77c87..27830bda2b 100644 --- a/src/frontend/tests/e2e-playwright/authorize/continue.spec.ts +++ b/src/frontend/tests/e2e-playwright/authorize/continue.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import { authorize, authorizeWithUrl, diff --git a/src/frontend/tests/e2e-playwright/authorize/delegationTtl.spec.ts b/src/frontend/tests/e2e-playwright/authorize/delegationTtl.spec.ts index 70d2b47fa2..fb987a3313 100644 --- a/src/frontend/tests/e2e-playwright/authorize/delegationTtl.spec.ts +++ b/src/frontend/tests/e2e-playwright/authorize/delegationTtl.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import { dummyAuth, II_URL, TEST_APP_URL } from "../utils"; test("Delegation maxTimeToLive: 1 min", async ({ page }) => { diff --git a/src/frontend/tests/e2e-playwright/authorize/index.spec.ts b/src/frontend/tests/e2e-playwright/authorize/index.spec.ts index 9a1e5233df..a00bb13fb7 100644 --- a/src/frontend/tests/e2e-playwright/authorize/index.spec.ts +++ b/src/frontend/tests/e2e-playwright/authorize/index.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import { authorize, authorizeWithUrl, diff --git a/src/frontend/tests/e2e-playwright/authorize/migration.spec.ts b/src/frontend/tests/e2e-playwright/authorize/migration.spec.ts index ec75470d9b..b020be20bc 100644 --- a/src/frontend/tests/e2e-playwright/authorize/migration.spec.ts +++ b/src/frontend/tests/e2e-playwright/authorize/migration.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import type Protocol from "devtools-protocol"; import { addCredentialToVirtualAuthenticator, diff --git a/src/frontend/tests/e2e-playwright/authorize/postMessages.spec.ts b/src/frontend/tests/e2e-playwright/authorize/postMessages.spec.ts index fca0d4f4e6..ece8aab538 100644 --- a/src/frontend/tests/e2e-playwright/authorize/postMessages.spec.ts +++ b/src/frontend/tests/e2e-playwright/authorize/postMessages.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import { createNewIdentityInII, dummyAuth, diff --git a/src/frontend/tests/e2e-playwright/dashboard/addPasskeys.spec.ts b/src/frontend/tests/e2e-playwright/dashboard/addPasskeys.spec.ts index faf69c2d2b..1d728c8b85 100644 --- a/src/frontend/tests/e2e-playwright/dashboard/addPasskeys.spec.ts +++ b/src/frontend/tests/e2e-playwright/dashboard/addPasskeys.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import { addPasskeyCurrentDevice, clearStorage, diff --git a/src/frontend/tests/e2e-playwright/dashboard/index.spec.ts b/src/frontend/tests/e2e-playwright/dashboard/index.spec.ts index d1ac76de9d..8041d7470c 100644 --- a/src/frontend/tests/e2e-playwright/dashboard/index.spec.ts +++ b/src/frontend/tests/e2e-playwright/dashboard/index.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import { clearStorage, createIdentity, dummyAuth, II_URL } from "../utils"; const TEST_USER_NAME = "Test User"; diff --git a/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts b/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts index 860098f4f0..d3b25e7341 100644 --- a/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts +++ b/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts @@ -1,4 +1,5 @@ -import { expect, Page, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; +import { Page } from "@playwright/test"; import { addVirtualAuthenticator, clearStorage, diff --git a/src/frontend/tests/e2e-playwright/dashboard/removePasskey.spec.ts b/src/frontend/tests/e2e-playwright/dashboard/removePasskey.spec.ts index 95556a1cfb..d5195f3321 100644 --- a/src/frontend/tests/e2e-playwright/dashboard/removePasskey.spec.ts +++ b/src/frontend/tests/e2e-playwright/dashboard/removePasskey.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import { clearStorage, createNewIdentityInII, diff --git a/src/frontend/tests/e2e-playwright/dashboard/renamePasskeys.spec.ts b/src/frontend/tests/e2e-playwright/dashboard/renamePasskeys.spec.ts index 266ce484bb..7e151fe0c3 100644 --- a/src/frontend/tests/e2e-playwright/dashboard/renamePasskeys.spec.ts +++ b/src/frontend/tests/e2e-playwright/dashboard/renamePasskeys.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "../fixtures"; import { clearStorage, createNewIdentityInII, diff --git a/src/frontend/tests/e2e-playwright/fixtures.ts b/src/frontend/tests/e2e-playwright/fixtures.ts new file mode 100644 index 0000000000..e3478210fd --- /dev/null +++ b/src/frontend/tests/e2e-playwright/fixtures.ts @@ -0,0 +1,37 @@ +import { test as base, expect } from "@playwright/test"; + +/** + * Custom test fixture that automatically applies host resolution routing + * to redirect all non-localhost requests to localhost:5173 + */ +export const test = base.extend({ + page: async ({ page }, use) => { + // Safari doesn't support --host-resolver-rules, so we need page routing for Safari + // Chromium browsers will use host-resolver-rules which preserves Host headers better + const browserName = page.context().browser()?.browserType().name(); + + if (browserName === "webkit") { + // Apply routing for Safari since it doesn't support host-resolver-rules + await page.route("**/*", (route) => { + const req = route.request(); + const urlStr = req.url(); + + let url: URL; + try { + url = new URL(urlStr); + } catch { + return route.continue(); + } + + const newUrl = `https://localhost:5173${url.pathname}${url.search}`; + return route.continue({ url: newUrl }); + }); + } + + // Use the page with routing applied + await use(page); + }, +}); + +// Re-export expect for convenience +export { expect }; diff --git a/src/frontend/tests/e2e-playwright/index.spec.ts b/src/frontend/tests/e2e-playwright/index.spec.ts index 4131274d71..0d10f5f9ad 100644 --- a/src/frontend/tests/e2e-playwright/index.spec.ts +++ b/src/frontend/tests/e2e-playwright/index.spec.ts @@ -1,4 +1,4 @@ -import { expect, test } from "@playwright/test"; +import { expect, test } from "./fixtures"; import { clearStorage, createIdentity, dummyAuth, II_URL } from "./utils"; // This is chosen on purpose to exhibit a JWT token that is encoded in base64url but cannot From cf459ac658d9e0111145952fb0253beccd6da88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Thu, 25 Sep 2025 13:07:28 +0200 Subject: [PATCH 05/15] Use Host header --- src/frontend/tests/e2e-playwright/fixtures.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/tests/e2e-playwright/fixtures.ts b/src/frontend/tests/e2e-playwright/fixtures.ts index e3478210fd..01f1799221 100644 --- a/src/frontend/tests/e2e-playwright/fixtures.ts +++ b/src/frontend/tests/e2e-playwright/fixtures.ts @@ -24,7 +24,8 @@ export const test = base.extend({ } const newUrl = `https://localhost:5173${url.pathname}${url.search}`; - return route.continue({ url: newUrl }); + // The vite server uses the Host header to determine where the redirect the request. + return route.continue({ url: newUrl, headers: { Host: url.hostname } }); }); } From 4914389dbc25f227af105c4058a6eb31c4fac1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Thu, 2 Oct 2025 10:43:49 +0200 Subject: [PATCH 06/15] Safari passing one test!!! --- src/frontend/tests/e2e-playwright/fixtures.ts | 14 +++++++++++--- src/internet_identity/src/http.rs | 8 ++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/frontend/tests/e2e-playwright/fixtures.ts b/src/frontend/tests/e2e-playwright/fixtures.ts index 01f1799221..420e8c542e 100644 --- a/src/frontend/tests/e2e-playwright/fixtures.ts +++ b/src/frontend/tests/e2e-playwright/fixtures.ts @@ -23,9 +23,17 @@ export const test = base.extend({ return route.continue(); } - const newUrl = `https://localhost:5173${url.pathname}${url.search}`; - // The vite server uses the Host header to determine where the redirect the request. - return route.continue({ url: newUrl, headers: { Host: url.hostname } }); + if (url.hostname.includes("localhost")) { + return route.continue(); + } + + // The vite server uses + const newUrl = `https://internet_identity.localhost:5173${url.pathname}${url.search}`; + return route.continue({ + url: newUrl, + // The vite server uses the Host header to determine where the redirect the request. + headers: { ...req.headers(), Host: url.hostname }, + }); }); } diff --git a/src/internet_identity/src/http.rs b/src/internet_identity/src/http.rs index cac29d0ff6..c62268756c 100644 --- a/src/internet_identity/src/http.rs +++ b/src/internet_identity/src/http.rs @@ -126,10 +126,10 @@ pub fn security_headers( // Content-Security-Policy (CSP) // Comprehensive policy to prevent XSS attacks and data injection // See content_security_policy_header() function for detailed explanation - ( - "Content-Security-Policy".to_string(), - content_security_policy_header(integrity_hashes, maybe_related_origins), - ), + // ( + // "Content-Security-Policy".to_string(), + // content_security_policy_header(integrity_hashes, maybe_related_origins), + // ), // Strict-Transport-Security (HSTS) // Forces browsers to use HTTPS for all future requests to this domain // max-age=31536000: Valid for 1 year (31,536,000 seconds) From 8de0844f6a77cc43931e14b001dd0659b82e0c2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Fri, 3 Oct 2025 09:28:00 +0200 Subject: [PATCH 07/15] Support other applications --- src/frontend/tests/e2e-playwright/fixtures.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/frontend/tests/e2e-playwright/fixtures.ts b/src/frontend/tests/e2e-playwright/fixtures.ts index 420e8c542e..6c93198793 100644 --- a/src/frontend/tests/e2e-playwright/fixtures.ts +++ b/src/frontend/tests/e2e-playwright/fixtures.ts @@ -12,7 +12,15 @@ export const test = base.extend({ if (browserName === "webkit") { // Apply routing for Safari since it doesn't support host-resolver-rules - await page.route("**/*", (route) => { + await page.context().route("**/*", (route) => { + // Should map the config in `vite.config.ts` + const hostToCanisterName: Record = { + ["id.ai"]: "internet_identity", + ["identity.ic0.app"]: "internet_identity", + ["identity.internetcomputer.org"]: "internet_identity", + ["nice-name.com"]: "test_app", + }; + const req = route.request(); const urlStr = req.url(); @@ -27,8 +35,12 @@ export const test = base.extend({ return route.continue(); } - // The vite server uses - const newUrl = `https://internet_identity.localhost:5173${url.pathname}${url.search}`; + const canister_name = hostToCanisterName[url.hostname]; + if (!canister_name) { + return route.continue(); + } + // The vite server uses the Host header and the localhost subdomain to determine where the redirect the request. + const newUrl = `https://${canister_name}.localhost:5173${url.pathname}${url.search}`; return route.continue({ url: newUrl, // The vite server uses the Host header to determine where the redirect the request. From 0340c0ff7f3977d406a6cdd0ca9078c0ca023ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Mon, 6 Oct 2025 11:05:19 +0200 Subject: [PATCH 08/15] Pass CI pipeline --- .../tests/e2e-playwright/authorize/migration.spec.ts | 5 +++++ .../tests/e2e-playwright/dashboard/migration.spec.ts | 5 +++++ src/frontend/tests/e2e-playwright/fixtures.ts | 2 +- src/internet_identity/src/http.rs | 8 ++++---- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/frontend/tests/e2e-playwright/authorize/migration.spec.ts b/src/frontend/tests/e2e-playwright/authorize/migration.spec.ts index b020be20bc..cf9823efde 100644 --- a/src/frontend/tests/e2e-playwright/authorize/migration.spec.ts +++ b/src/frontend/tests/e2e-playwright/authorize/migration.spec.ts @@ -15,6 +15,11 @@ import { isNullish } from "@dfinity/utils"; const TEST_USER_NAME = "Test User"; test.describe("Migration from an app", () => { + test.skip( + ({ browserName }) => browserName === "webkit", + "Migration test not supported on Safari because it uses virtual authenticators which are not supported.", + ); + test("User can migrate a legacy identity", async ({ page }) => { const auth = dummyAuth(); let credential: Protocol.WebAuthn.Credential | undefined; diff --git a/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts b/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts index d3b25e7341..9a4ce6d3e3 100644 --- a/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts +++ b/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts @@ -39,6 +39,11 @@ const upgradeLegacyIdentity = async ( }; test.describe("Migration", () => { + test.skip( + ({ browserName }) => browserName === "webkit", + "Migration test not supported on Safari because it uses virtual authenticators which are not supported.", + ); + test("User can migrate a legacy identity", async ({ page }) => { // Step 1: Create a legacy identity await page.goto(LEGACY_II_URL); diff --git a/src/frontend/tests/e2e-playwright/fixtures.ts b/src/frontend/tests/e2e-playwright/fixtures.ts index 6c93198793..caa065f686 100644 --- a/src/frontend/tests/e2e-playwright/fixtures.ts +++ b/src/frontend/tests/e2e-playwright/fixtures.ts @@ -36,7 +36,7 @@ export const test = base.extend({ } const canister_name = hostToCanisterName[url.hostname]; - if (!canister_name) { + if (canister_name === undefined) { return route.continue(); } // The vite server uses the Host header and the localhost subdomain to determine where the redirect the request. diff --git a/src/internet_identity/src/http.rs b/src/internet_identity/src/http.rs index c62268756c..cac29d0ff6 100644 --- a/src/internet_identity/src/http.rs +++ b/src/internet_identity/src/http.rs @@ -126,10 +126,10 @@ pub fn security_headers( // Content-Security-Policy (CSP) // Comprehensive policy to prevent XSS attacks and data injection // See content_security_policy_header() function for detailed explanation - // ( - // "Content-Security-Policy".to_string(), - // content_security_policy_header(integrity_hashes, maybe_related_origins), - // ), + ( + "Content-Security-Policy".to_string(), + content_security_policy_header(integrity_hashes, maybe_related_origins), + ), // Strict-Transport-Security (HSTS) // Forces browsers to use HTTPS for all future requests to this domain // max-age=31536000: Valid for 1 year (31,536,000 seconds) From 6a1d23f4a17d8cc18b37b94929cbf83d8ff5738a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Mon, 6 Oct 2025 12:08:24 +0200 Subject: [PATCH 09/15] Enforce using fixtures test and expect --- HACKING.md | 16 ++++++++++++++++ eslint.config.js | 18 ++++++++++++++++++ src/frontend/tests/e2e-playwright/fixtures.ts | 12 ++++++++++++ 3 files changed, 46 insertions(+) diff --git a/HACKING.md b/HACKING.md index f90c169a62..1d0d5ec56a 100644 --- a/HACKING.md +++ b/HACKING.md @@ -136,6 +136,22 @@ npx playwright test --ui > II_CAPTCHA=enabled npm run test:e2e > ``` +**Writing new Playwright E2E tests:** + +When creating new E2E Playwright tests, always import `test` and `expect` from the custom fixtures file: + +```typescript +// ✅ Correct +import { test, expect } from "./fixtures"; +// or from subdirectories: +import { test, expect } from "../fixtures"; + +// ❌ Wrong - will fail ESLint +import { test, expect } from "@playwright/test"; +``` + +The custom fixtures provide automatic host routing for Safari/WebKit tests, which don't support `--host-resolver-rules`. ESLint will enforce this pattern and show an error if you try to import directly from `@playwright/test`. + We autoformat our code using `prettier`. Running `npm run format` formats all files in the frontend. If you open a PR that isn't formatted according to `prettier`, CI will automatically add a formatting commit to your PR. diff --git a/eslint.config.js b/eslint.config.js index b627db37c5..72b98e313b 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -60,4 +60,22 @@ export default ts.config( { ignores: ["src/frontend/src/lib/generated/*", "src/showcase/.astro/*"], }, + { + files: ["src/frontend/tests/e2e-playwright/**/*.spec.ts"], + rules: { + "no-restricted-imports": [ + "error", + { + paths: [ + { + name: "@playwright/test", + importNames: ["test", "expect"], + message: + "Import 'test' and 'expect' from './fixtures' or '../fixtures' instead to use custom test fixtures with host routing for Safari/WebKit.", + }, + ], + }, + ], + }, + }, ); diff --git a/src/frontend/tests/e2e-playwright/fixtures.ts b/src/frontend/tests/e2e-playwright/fixtures.ts index caa065f686..471397d41b 100644 --- a/src/frontend/tests/e2e-playwright/fixtures.ts +++ b/src/frontend/tests/e2e-playwright/fixtures.ts @@ -3,6 +3,18 @@ import { test as base, expect } from "@playwright/test"; /** * Custom test fixture that automatically applies host resolution routing * to redirect all non-localhost requests to localhost:5173 + * + * ⚠️ IMPORTANT: All E2E test files MUST import { test, expect } from this file + * instead of from '@playwright/test' to ensure proper host routing for Safari/WebKit. + * + * @example + * // ✅ Correct + * import { test, expect } from "./fixtures"; + * // or from subdirectories: + * import { test, expect } from "../fixtures"; + * + * // ❌ Wrong - will fail ESLint + * import { test, expect } from "@playwright/test"; */ export const test = base.extend({ page: async ({ page }, use) => { From 429388b79a491119ebf1e623bbe44b981bf2d584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Mon, 6 Oct 2025 12:18:46 +0200 Subject: [PATCH 10/15] Use dev CSP for E2E and extend it --- .github/workflows/canister-tests.yml | 2 +- src/internet_identity/src/http.rs | 21 +++++++++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/canister-tests.yml b/.github/workflows/canister-tests.yml index 5e5edfba25..f3723b5afe 100644 --- a/.github/workflows/canister-tests.yml +++ b/.github/workflows/canister-tests.yml @@ -57,7 +57,7 @@ jobs: II_FETCH_ROOT_KEY: 1 II_DUMMY_CAPTCHA: 1 II_DUMMY_AUTH: 0 - II_DEV_CSP: 0 + II_DEV_CSP: 1 # Everything disabled, used by third party developers who only care # about the login flow diff --git a/src/internet_identity/src/http.rs b/src/internet_identity/src/http.rs index cac29d0ff6..11d2afb1a7 100644 --- a/src/internet_identity/src/http.rs +++ b/src/internet_identity/src/http.rs @@ -259,10 +259,19 @@ fn content_security_policy_header( }; let connect_src = "'self' https:"; + let script_src = format!("{strict_dynamic} 'unsafe-inline' 'unsafe-eval' https:"); + let style_src = "'self' 'unsafe-inline'"; + let img_src = "'self' data: https://*.googleusercontent.com"; - // Allow connecting via http for development purposes + // Allow connecting via http and localhost (including subdomains) for development purposes #[cfg(feature = "dev_csp")] - let connect_src = format!("{connect_src} http:"); + let connect_src = format!("{connect_src} http: http://localhost:* http://*.localhost:*"); + #[cfg(feature = "dev_csp")] + let script_src = format!("{script_src} http: http://localhost:* http://*.localhost:*"); + #[cfg(feature = "dev_csp")] + let style_src = format!("{style_src} http: http://localhost:* http://*.localhost:*"); + #[cfg(feature = "dev_csp")] + let img_src = format!("{img_src} http: http://localhost:* http://*.localhost:*"); // Allow related origins to embed one another for cross-domain WebAuthn let frame_src = maybe_related_origins @@ -273,12 +282,12 @@ fn content_security_policy_header( let csp = format!( "default-src 'none';\ connect-src {connect_src};\ - img-src 'self' data: https://*.googleusercontent.com;\ - script-src {strict_dynamic} 'unsafe-inline' 'unsafe-eval' https:;\ + img-src {img_src};\ + script-src {script_src};\ base-uri 'none';\ form-action 'none';\ - style-src 'self' 'unsafe-inline';\ - style-src-elem 'self' 'unsafe-inline';\ + style-src {style_src};\ + style-src-elem {style_src};\ font-src 'self';\ frame-ancestors {frame_src};\ frame-src {frame_src};" From 0e6c2a1dfb4996ad32b7162519d1bf7a2734f47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Mon, 6 Oct 2025 14:27:26 +0200 Subject: [PATCH 11/15] Improve comments and remove unnecessary config --- playwright.config.ts | 2 -- src/frontend/tests/e2e-playwright/fixtures.ts | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/playwright.config.ts b/playwright.config.ts index 5613ac9f26..968bc73a6d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -30,8 +30,6 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", - /* Accept self-signed certificates when dev server runs with TLS */ - ignoreHTTPSErrors: true, }, timeout: 60000, diff --git a/src/frontend/tests/e2e-playwright/fixtures.ts b/src/frontend/tests/e2e-playwright/fixtures.ts index 471397d41b..d3db0f25e4 100644 --- a/src/frontend/tests/e2e-playwright/fixtures.ts +++ b/src/frontend/tests/e2e-playwright/fixtures.ts @@ -18,12 +18,11 @@ import { test as base, expect } from "@playwright/test"; */ export const test = base.extend({ page: async ({ page }, use) => { - // Safari doesn't support --host-resolver-rules, so we need page routing for Safari - // Chromium browsers will use host-resolver-rules which preserves Host headers better const browserName = page.context().browser()?.browserType().name(); + // Safari doesn't support --host-resolver-rules, so we need page routing for Safari + // Chromium browsers will use host-resolver-rules which preserves Host headers better if (browserName === "webkit") { - // Apply routing for Safari since it doesn't support host-resolver-rules await page.context().route("**/*", (route) => { // Should map the config in `vite.config.ts` const hostToCanisterName: Record = { @@ -51,7 +50,6 @@ export const test = base.extend({ if (canister_name === undefined) { return route.continue(); } - // The vite server uses the Host header and the localhost subdomain to determine where the redirect the request. const newUrl = `https://${canister_name}.localhost:5173${url.pathname}${url.search}`; return route.continue({ url: newUrl, From dae8da0e534ceab51b378381d43f81f95b15f4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Mon, 6 Oct 2025 14:38:29 +0200 Subject: [PATCH 12/15] Use import type --- src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts b/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts index 9a4ce6d3e3..f04cd9dd6a 100644 --- a/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts +++ b/src/frontend/tests/e2e-playwright/dashboard/migration.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from "../fixtures"; -import { Page } from "@playwright/test"; +import type { Page } from "@playwright/test"; import { addVirtualAuthenticator, clearStorage, From f94d3656b3878c2f62cda7fe6a74f7d58ea1dcd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Mon, 6 Oct 2025 14:47:34 +0200 Subject: [PATCH 13/15] Reintroduce `ignoreHTTPSErrors` from playwright --- playwright.config.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/playwright.config.ts b/playwright.config.ts index 968bc73a6d..ca5b5a8e43 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -30,6 +30,9 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: "on-first-retry", + + /* Ignore HTTPS errors, which is needed for the self-signed certificate. */ + ignoreHTTPSErrors: true, }, timeout: 60000, From 8310265ff83ec8a486211a98275fad8229ade4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Mon, 6 Oct 2025 14:59:38 +0200 Subject: [PATCH 14/15] Ignore certificate errors --- playwright.config.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/playwright.config.ts b/playwright.config.ts index ca5b5a8e43..e18e403d66 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -66,12 +66,18 @@ export default defineConfig({ name: "safari-desktop", use: { ...devices["Desktop Safari"], + launchOptions: { + args: ["--ignore-certificate-errors"], + }, }, }, { name: "safari-mobile", use: { ...devices["iPhone 12"], + launchOptions: { + args: ["--ignore-certificate-errors"], + }, }, }, ], From 601f5ca1d917695aef30dd8f387aec8732b456c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lloren=C3=A7?= Date: Mon, 6 Oct 2025 15:01:11 +0200 Subject: [PATCH 15/15] Fix --- playwright.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index e18e403d66..1a4453591f 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -32,7 +32,8 @@ export default defineConfig({ trace: "on-first-retry", /* Ignore HTTPS errors, which is needed for the self-signed certificate. */ - ignoreHTTPSErrors: true, + // It doesn't seem to fix it + // ignoreHTTPSErrors: true, }, timeout: 60000,