diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe2959a354c5..a246ce645389 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,9 +38,44 @@ jobs: strategy: fail-fast: false matrix: - node-version: [16] - os: [ubuntu-latest, windows-latest] - e2e-browser: ['chromium'] + include: + - node-version: 16 + os: [ubuntu-latest, windows-latest] + e2e-browser: 'chromium' + - node-version: 18 + os: ubuntu-latest + e2e-browser: 'chromium' + env: + TURBO_CACHE_KEY: ${{ matrix.os }}-${{ matrix.node-version }} + KIT_E2E_BROWSER: ${{matrix.e2e-browser}} + steps: + - run: git config --global core.autocrlf false + - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v2.2.4 + - uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm playwright install ${{ matrix.e2e-browser }} + - run: pnpm test + - name: Archive test results + if: failure() + shell: bash + run: find packages -type d -name test-results -not -empty | tar -czf test-results.tar.gz --files-from=- + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v3 + with: + retention-days: 3 + name: test-failure-${{ github.run_id }}-${{ matrix.os }}-${{ matrix.node-version }}-${{ matrix.e2e-browser }} + path: test-results.tar.gz + Cross-browser-test: + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: include: - node-version: 16 os: ubuntu-latest @@ -48,9 +83,6 @@ jobs: - node-version: 16 os: macOS-latest e2e-browser: 'webkit' - - node-version: 18 - os: ubuntu-latest - e2e-browser: 'chromium' env: TURBO_CACHE_KEY: ${{ matrix.os }}-${{ matrix.node-version }} KIT_E2E_BROWSER: ${{matrix.e2e-browser}} @@ -64,7 +96,7 @@ jobs: cache: pnpm - run: pnpm install --frozen-lockfile - run: pnpm playwright install ${{ matrix.e2e-browser }} - - run: pnpm test + - run: pnpm test:cross-browser - name: Archive test results if: failure() shell: bash diff --git a/package.json b/package.json index 70b9771dcc95..e50d3a5cffe3 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "private": true, "scripts": { "test": "turbo run test --filter=./packages/*", + "test:cross-browser": "turbo run test:cross-browser --filter=./packages/*", "test:vite-ecosystem-ci": "pnpm test --dir packages/kit", "check": "turbo run check", "lint": "turbo run lint", diff --git a/packages/kit/package.json b/packages/kit/package.json index a3ca83698a58..31078be85a12 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -63,6 +63,7 @@ "format": "pnpm lint --write", "test": "pnpm test:unit && pnpm test:integration", "test:integration": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test", + "test:cross-browser": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-browser", "test:unit": "uvu src \"(spec\\.js|test[\\\\/]index\\.js)\"", "postinstall": "node postinstall.js" }, diff --git a/packages/kit/test/apps/basics/package.json b/packages/kit/test/apps/basics/package.json index 6257ff4b4556..0fe2c3750a4a 100644 --- a/packages/kit/test/apps/basics/package.json +++ b/packages/kit/test/apps/basics/package.json @@ -9,7 +9,10 @@ "check": "svelte-kit sync && tsc && svelte-check", "test": "node test/setup.js && pnpm test:dev && pnpm test:build", "test:dev": "rimraf test/errors.json && cross-env DEV=true playwright test", - "test:build": "rimraf test/errors.json && playwright test" + "test:build": "rimraf test/errors.json && playwright test", + "test:cross-browser": "npm run test:cross-browser:dev && npm run test:cross-browser:build", + "test:cross-browser:dev": "node test/setup.js && rimraf test/errors.json && cross-env DEV=true playwright test test/cross-browser/", + "test:cross-browser:build": "node test/setup.js && rimraf test/errors.json && playwright test test/cross-browser/" }, "devDependencies": { "@sveltejs/kit": "workspace:^", diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js index f4fa88f8008a..4ff03270be9b 100644 --- a/packages/kit/test/apps/basics/test/client.test.js +++ b/packages/kit/test/apps/basics/test/client.test.js @@ -8,95 +8,13 @@ test.skip(({ javaScriptEnabled }) => !javaScriptEnabled); test.describe.configure({ mode: 'parallel' }); test.describe('a11y', () => { - test('resets focus', async ({ page, clicknav, browserName }) => { - const tab = browserName === 'webkit' ? 'Alt+Tab' : 'Tab'; + test('applies autofocus after an enhanced form submit', async ({ page }) => { + await page.goto('/accessibility/autofocus/b'); - await page.goto('/accessibility/a'); - - await clicknav('[href="/accessibility/b"]'); - expect(await page.innerHTML('h1')).toBe('b'); - expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('BODY'); - await page.keyboard.press(tab); - - expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('BUTTON'); - expect(await page.evaluate(() => (document.activeElement || {}).textContent)).toBe('focus me'); - - await clicknav('[href="/accessibility/a"]'); - expect(await page.innerHTML('h1')).toBe('a'); - expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('BODY'); - - await page.keyboard.press(tab); - expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('BUTTON'); - expect(await page.evaluate(() => (document.activeElement || {}).textContent)).toBe('focus me'); - - expect(await page.evaluate(() => document.documentElement.getAttribute('tabindex'))).toBe(null); - }); - - test('applies autofocus after a navigation', async ({ page, clicknav }) => { - await page.goto('/accessibility/autofocus/a'); - - await clicknav('[href="/accessibility/autofocus/b"]'); - expect(await page.innerHTML('h1')).toBe('b'); - expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('INPUT'); - }); - - (process.env.KIT_E2E_BROWSER === 'webkit' ? test.skip : test)( - 'applies autofocus after an enhanced form submit', - async ({ page }) => { - await page.goto('/accessibility/autofocus/b'); - - await page.click('#submit'); - await page.waitForFunction(() => document.activeElement?.nodeName === 'INPUT', null, { - timeout: 1000 - }); - } - ); - - test('announces client-side navigation', async ({ page, clicknav, javaScriptEnabled }) => { - await page.goto('/accessibility/a'); - - const has_live_region = (await page.innerHTML('body')).includes('aria-live'); - - if (javaScriptEnabled) { - expect(has_live_region).toBeTruthy(); - - // live region should exist, but be empty - expect(await page.innerHTML('[aria-live]')).toBe(''); - - await clicknav('[href="/accessibility/b"]'); - expect(await page.innerHTML('[aria-live]')).toBe('b'); // TODO i18n - } else { - expect(has_live_region).toBeFalsy(); - } - }); - - test('reset selection', async ({ page, clicknav }) => { - await page.goto('/selection/a'); - - expect( - await page.evaluate(() => { - const range = document.createRange(); - range.selectNodeContents(document.body); - const selection = getSelection(); - if (selection) { - selection.removeAllRanges(); - selection.addRange(range); - return selection.rangeCount; - } - return -1; - }) - ).toBe(1); - - await clicknav('[href="/selection/b"]'); - expect( - await page.evaluate(() => { - const selection = getSelection(); - if (selection) { - return selection.rangeCount; - } - return -1; - }) - ).toBe(0); + await page.click('#submit'); + await page.waitForFunction(() => document.activeElement?.nodeName === 'INPUT', null, { + timeout: 1000 + }); }); }); @@ -113,294 +31,6 @@ test.describe('Caching', () => { }); }); -test.describe('beforeNavigate', () => { - test('prevents navigation triggered by link click', async ({ page, baseURL }) => { - await page.goto('/before-navigate/prevent-navigation'); - - await page.click('[href="/before-navigate/a"]'); - await page.waitForLoadState('networkidle'); - - expect(page.url()).toBe(baseURL + '/before-navigate/prevent-navigation'); - expect(await page.innerHTML('pre')).toBe('1 false link'); - }); - - test('prevents navigation to external', async ({ page, baseURL }) => { - await page.goto('/before-navigate/prevent-navigation'); - await page.click('h1'); // The browsers block attempts to prevent navigation on a frame that's never had a user gesture. - - page.on('dialog', (dialog) => dialog.dismiss()); - - page.click('a[href="https://google.de"]'); // do NOT await this, promise only resolves after successful navigation, which never happens - await page.waitForTimeout(500); - await expect(page.locator('pre')).toHaveText('1 true link'); - expect(page.url()).toBe(baseURL + '/before-navigate/prevent-navigation'); - }); - - test('prevents navigation triggered by goto', async ({ page, app, baseURL }) => { - await page.goto('/before-navigate/prevent-navigation'); - await app.goto('/before-navigate/a'); - expect(page.url()).toBe(baseURL + '/before-navigate/prevent-navigation'); - expect(await page.innerHTML('pre')).toBe('1 false goto'); - }); - - test('prevents external navigation triggered by goto', async ({ page, app, baseURL }) => { - await page.goto('/before-navigate/prevent-navigation'); - await app.goto('https://google.de'); - expect(page.url()).toBe(baseURL + '/before-navigate/prevent-navigation'); - expect(await page.innerHTML('pre')).toBe('1 true goto'); - }); - - test('prevents navigation triggered by back button', async ({ page, app, baseURL }) => { - await page.goto('/before-navigate/a'); - await app.goto('/before-navigate/prevent-navigation'); - await page.click('h1'); // The browsers block attempts to prevent navigation on a frame that's never had a user gesture. - - await page.goBack(); - expect(await page.innerHTML('pre')).toBe('1 false popstate'); - expect(page.url()).toBe(baseURL + '/before-navigate/prevent-navigation'); - }); - - test('prevents unload', async ({ page }) => { - await page.goto('/before-navigate/prevent-navigation'); - await page.click('h1'); // The browsers block attempts to prevent navigation on a frame that's never had a user gesture. - const type = new Promise((fulfil) => { - page.on('dialog', async (dialog) => { - fulfil(dialog.type()); - await dialog.dismiss(); - }); - }); - - await page.close({ runBeforeUnload: true }); - expect(await type).toBe('beforeunload'); - expect(await page.innerHTML('pre')).toBe('1 true leave'); - }); - - test('is not triggered on redirect', async ({ page, baseURL }) => { - await page.goto('/before-navigate/prevent-navigation'); - - await page.click('[href="/before-navigate/redirect"]'); - await page.waitForLoadState('networkidle'); - - expect(page.url()).toBe(baseURL + '/before-navigate/prevent-navigation'); - expect(await page.innerHTML('pre')).toBe('1 false link'); - }); - - test('is not triggered on target=_blank', async ({ page, baseURL }) => { - await page.goto('/before-navigate/prevent-navigation'); - - await page.click('a[href="https://google.com"]'); - await page.waitForTimeout(500); - - expect(page.url()).toBe(baseURL + '/before-navigate/prevent-navigation'); - expect(await page.innerHTML('pre')).toBe('0 false undefined'); - }); -}); - -test.describe('Scrolling', () => { - test('url-supplied anchor works on direct page load', async ({ page, in_view }) => { - await page.goto('/anchor/anchor#go-to-element'); - expect(await in_view('#go-to-element')).toBe(true); - }); - - test('url-supplied anchor works on navigation to page', async ({ page, in_view, clicknav }) => { - await page.goto('/anchor'); - await clicknav('#first-anchor'); - expect(await in_view('#go-to-element')).toBe(true); - }); - - test('url-supplied anchor works when navigated from scrolled page', async ({ - page, - clicknav, - in_view - }) => { - await page.goto('/anchor'); - await clicknav('#second-anchor'); - expect(await in_view('#go-to-element')).toBe(true); - }); - - test('no-anchor url will scroll to top when navigated from scrolled page', async ({ - page, - clicknav - }) => { - await page.goto('/anchor'); - await clicknav('#third-anchor'); - expect(await page.evaluate(() => scrollY === 0)).toBeTruthy(); - }); - - test('url-supplied anchor works when navigated from bottom of page', async ({ - page, - clicknav, - in_view - }) => { - await page.goto('/anchor'); - await clicknav('#last-anchor'); - expect(await in_view('#go-to-element')).toBe(true); - }); - - test('no-anchor url will scroll to top when navigated from bottom of page', async ({ - clicknav, - page - }) => { - await page.goto('/anchor'); - await clicknav('#last-anchor-2'); - expect(await page.evaluate(() => scrollY === 0)).toBeTruthy(); - }); - - test('scroll is restored after hitting the back button', async ({ baseURL, clicknav, page }) => { - await page.goto('/anchor'); - await page.locator('#scroll-anchor').click(); - const originalScrollY = /** @type {number} */ (await page.evaluate(() => scrollY)); - await clicknav('#routing-page'); - await page.goBack(); - await page.waitForLoadState('networkidle'); - expect(page.url()).toBe(baseURL + '/anchor#last-anchor-2'); - expect(await page.evaluate(() => scrollY)).toEqual(originalScrollY); - - await page.goBack(); - await page.waitForLoadState('networkidle'); - - expect(page.url()).toBe(baseURL + '/anchor'); - expect(await page.evaluate(() => scrollY)).toEqual(0); - }); - - test('scroll is restored after hitting the back button for an in-app cross-document navigation', async ({ - page, - clicknav - }) => { - await page.goto('/scroll/cross-document/a'); - - const rect = await page.locator('[href="/scroll/cross-document/b"]').boundingBox(); - const height = await page.evaluate(() => innerHeight); - if (!rect) throw new Error('Could not determine bounding box'); - - const target_scroll_y = rect.y + rect.height - height; - await page.evaluate((y) => scrollTo(0, y), target_scroll_y); - - await page.locator('[href="/scroll/cross-document/b"]').click(); - expect(await page.textContent('h1')).toBe('b'); - await page.waitForSelector('body.started'); - - await clicknav('[href="/scroll/cross-document/c"]'); - expect(await page.textContent('h1')).toBe('c'); - - await page.goBack(); // client-side back - await page.goBack(); // native back - expect(await page.textContent('h1')).toBe('a'); - await page.waitForSelector('body.started'); - - await page.waitForTimeout(250); // needed for the test to fail reliably without the fix - - const scroll_y = await page.evaluate(() => scrollY); - - expect(Math.abs(scroll_y - target_scroll_y)).toBeLessThan(50); // we need a few pixels wiggle room, because browsers - }); - - test('url-supplied anchor is ignored with onMount() scrolling on direct page load', async ({ - page, - in_view - }) => { - await page.goto('/anchor-with-manual-scroll/anchor-onmount#go-to-element'); - expect(await in_view('#abcde')).toBe(true); - }); - - test('url-supplied anchor is ignored with afterNavigate() scrolling on direct page load', async ({ - page, - in_view, - clicknav - }) => { - await page.goto('/anchor-with-manual-scroll/anchor-afternavigate#go-to-element'); - expect(await in_view('#abcde')).toBe(true); - - await clicknav('[href="/anchor-with-manual-scroll/anchor-afternavigate?x=y#go-to-element"]'); - expect(await in_view('#abcde')).toBe(true); - }); - - test('url-supplied anchor is ignored with onMount() scrolling on navigation to page', async ({ - page, - clicknav, - javaScriptEnabled, - in_view - }) => { - await page.goto('/anchor-with-manual-scroll'); - await clicknav('[href="/anchor-with-manual-scroll/anchor-onmount#go-to-element"]'); - if (javaScriptEnabled) expect(await in_view('#abcde')).toBe(true); - else expect(await in_view('#go-to-element')).toBe(true); - }); - - test('app-supplied scroll and focus work on direct page load', async ({ page, in_view }) => { - await page.goto('/use-action/focus-and-scroll'); - expect(await in_view('#input')).toBe(true); - await expect(page.locator('#input')).toBeFocused(); - }); - - test('app-supplied scroll and focus work on navigation to page', async ({ - page, - clicknav, - in_view - }) => { - await page.goto('/use-action'); - await clicknav('[href="/use-action/focus-and-scroll"]'); - expect(await in_view('#input')).toBe(true); - await expect(page.locator('input')).toBeFocused(); - }); - - test('scroll positions are recovered on reloading the page', async ({ page, app }) => { - await page.goto('/anchor'); - await page.evaluate(() => window.scrollTo(0, 1000)); - await app.goto('/anchor/anchor'); - await page.evaluate(() => window.scrollTo(0, 1000)); - - await page.reload(); - expect(await page.evaluate(() => window.scrollY)).toBe(1000); - - await page.goBack(); - expect(await page.evaluate(() => window.scrollY)).toBe(1000); - }); - - test('scroll position is top of page on ssr:false reload', async ({ page }) => { - await page.goto('/no-ssr/margin'); - expect(await page.evaluate(() => window.scrollY)).toBe(0); - await page.reload(); - expect(await page.evaluate(() => window.scrollY)).toBe(0); - }); -}); - -test.describe('afterNavigate', () => { - test('calls callback', async ({ page, clicknav }) => { - await page.goto('/after-navigate/a'); - expect(await page.textContent('h1')).toBe('undefined -> /after-navigate/a'); - - await clicknav('[href="/after-navigate/b"]'); - expect(await page.textContent('h1')).toBe('/after-navigate/a -> /after-navigate/b'); - }); -}); - -test.describe('a11y', () => { - test('keepfocus works', async ({ page }) => { - await page.goto('/keepfocus'); - - await Promise.all([ - page.locator('#input').fill('bar'), - page.waitForFunction(() => window.location.search === '?foo=bar') - ]); - await expect(page.locator('#input')).toBeFocused(); - }); -}); - -test.describe('CSS', () => { - test('applies generated component styles (hides announcer)', async ({ page, clicknav }) => { - await page.goto('/css'); - await clicknav('[href="/css/other"]'); - - expect( - await page.evaluate(() => { - const el = document.querySelector('#svelte-announcer'); - return el && getComputedStyle(el).position; - }) - ).toBe('absolute'); - }); -}); - test.describe('Endpoints', () => { test('calls a delete handler', async ({ page }) => { await page.goto('/delete-route'); @@ -409,72 +39,6 @@ test.describe('Endpoints', () => { }); }); -test.describe.serial('Errors', () => { - test('client-side load errors', async ({ page }) => { - await page.goto('/errors/load-client'); - - expect(await page.textContent('footer')).toBe('Custom layout'); - expect(await page.textContent('#message')).toBe( - 'This is your custom error page saying: "Crashing now"' - ); - }); - - test('client-side module context errors', async ({ page }) => { - await page.goto('/errors/module-scope-client'); - - expect(await page.textContent('footer')).toBe('Custom layout'); - expect(await page.textContent('#message')).toBe( - 'This is your custom error page saying: "Crashing now"' - ); - }); - - test('client-side error from load()', async ({ page }) => { - await page.goto('/errors/load-error-client'); - - expect(await page.textContent('footer')).toBe('Custom layout'); - expect(await page.textContent('#message')).toBe( - 'This is your custom error page saying: "Not found"' - ); - expect(await page.innerHTML('h1')).toBe('555'); - }); - - test('client-side 4xx status without error from load()', async ({ page }) => { - await page.goto('/errors/load-status-without-error-client'); - - expect(await page.textContent('footer')).toBe('Custom layout'); - expect(await page.textContent('#message')).toBe( - 'This is your custom error page saying: "Error: 401"' - ); - expect(await page.innerHTML('h1')).toBe('401'); - }); - - test('Root error falls back to error.html (unexpected error)', async ({ page, clicknav }) => { - await page.goto('/errors/error-html'); - await clicknav('button:text-is("Unexpected")'); - - expect(await page.textContent('h1')).toBe('Error - 500'); - expect(await page.textContent('p')).toBe( - 'This is the static error page with the following message: Failed to load' - ); - }); - - test('Root error falls back to error.html (expected error)', async ({ page, clicknav }) => { - await page.goto('/errors/error-html'); - await clicknav('button:text-is("Expected")'); - - expect(await page.textContent('h1')).toBe('Error - 401'); - expect(await page.textContent('p')).toBe( - 'This is the static error page with the following message: Not allowed' - ); - }); - - test('Root 404 redirects somewhere due to root layout', async ({ page, baseURL, clicknav }) => { - await page.goto('/errors/error-html'); - await clicknav('button:text-is("Redirect")'); - expect(page.url()).toBe(baseURL + '/load'); - }); -}); - test.describe('Load', () => { test('load function is only called when necessary', async ({ app, page }) => { await page.goto('/load/change-detection/one/a'); @@ -680,207 +244,6 @@ test.describe('Page options', () => { }); }); -test.describe('Prefetching', () => { - test('prefetches programmatically', async ({ baseURL, page, app }) => { - await page.goto('/routing/a'); - - /** @type {string[]} */ - let requests = []; - page.on('request', (r) => requests.push(r.url())); - - // also wait for network processing to complete, see - // https://playwright.dev/docs/network#network-events - await Promise.all([ - page.waitForResponse(`${baseURL}/routing/preloading/preloaded.json`), - app.preloadData('/routing/preloading/preloaded') - ]); - - // svelte request made is environment dependent - if (process.env.DEV) { - expect(requests.filter((req) => req.endsWith('+page.svelte')).length).toBe(1); - } else { - // the preload helper causes an additional request to be made in Firefox, - // so we use toBeGreaterThan rather than toBe - expect(requests.filter((req) => req.endsWith('.js')).length).toBeGreaterThan(0); - } - - expect(requests.includes(`${baseURL}/routing/preloading/preloaded.json`)).toBe(true); - - requests = []; - await app.goto('/routing/preloading/preloaded'); - expect(requests).toEqual([]); - - try { - await app.preloadData('https://example.com'); - throw new Error('Error was not thrown'); - } catch (/** @type {any} */ e) { - expect(e.message).toMatch('Attempted to preload a URL that does not belong to this app'); - } - }); - - test('chooses correct route when hash route is preloaded but regular route is clicked', async ({ - app, - page - }) => { - await page.goto('/routing/a'); - await app.preloadData('/routing/preloading/hash-route#please-dont-show-me'); - await app.goto('/routing/preloading/hash-route'); - await expect(page.locator('h1')).not.toHaveText('Oopsie'); - }); - - test('does not rerun load on calls to duplicate preload hash route', async ({ app, page }) => { - await page.goto('/routing/a'); - - await app.preloadData('/routing/preloading/hash-route#please-dont-show-me'); - await app.preloadData('/routing/preloading/hash-route#please-dont-show-me'); - await app.goto('/routing/preloading/hash-route#please-dont-show-me'); - await expect(page.locator('p')).toHaveText('Loaded 1 times.'); - }); - - test('does not rerun load on calls to different preload hash route', async ({ app, page }) => { - await page.goto('/routing/a'); - - await app.preloadData('/routing/preloading/hash-route#please-dont-show-me'); - await app.preloadData('/routing/preloading/hash-route#please-dont-show-me-jr'); - await app.goto('/routing/preloading/hash-route#please-dont-show-me'); - await expect(page.locator('p')).toHaveText('Loaded 1 times.'); - }); - - test('does rerun load when preload errored', async ({ app, page }) => { - await page.goto('/routing/a'); - - await app.preloadData('/routing/preloading/preload-error'); - await app.goto('/routing/preloading/preload-error'); - await expect(page.locator('p')).toHaveText('hello'); - }); -}); - -test.describe('Routing', () => { - test('navigates to a new page without reloading', async ({ app, page, clicknav }) => { - await page.goto('/routing'); - - await app.preloadData('/routing/a').catch((e) => { - // from error handler tests; ignore - if (!e.message.includes('Crashing now')) throw e; - }); - - /** @type {string[]} */ - const requests = []; - page.on('request', (r) => requests.push(r.url())); - - await clicknav('a[href="/routing/a"]'); - expect(await page.textContent('h1')).toBe('a'); - - expect(requests.filter((url) => !url.endsWith('/favicon.png'))).toEqual([]); - }); - - test('navigates programmatically', async ({ page, app }) => { - await page.goto('/routing/a'); - await app.goto('/routing/b'); - expect(await page.textContent('h1')).toBe('b'); - }); - - test('$page.url.hash is correctly set on page load', async ({ page }) => { - await page.goto('/routing/hashes/pagestore#target'); - expect(await page.textContent('#window-hash')).toBe('#target'); - expect(await page.textContent('#page-url-hash')).toBe('#target'); - }); - - test('$page.url.hash is correctly set on navigation', async ({ page }) => { - await page.goto('/routing/hashes/pagestore'); - expect(await page.textContent('#window-hash')).toBe(''); - expect(await page.textContent('#page-url-hash')).toBe(''); - await page.locator('[href="#target"]').click(); - expect(await page.textContent('#window-hash')).toBe('#target'); - expect(await page.textContent('#page-url-hash')).toBe('#target'); - await page.locator('[href="/routing/hashes/pagestore"]').click(); - await expect(page.locator('#window-hash')).toHaveText('#target'); // hashchange doesn't fire for these - await expect(page.locator('#page-url-hash')).toHaveText(''); - }); - - test('does not normalize external path', async ({ page, start_server }) => { - const html_ok = '
ok'; - const { port } = await start_server((_req, res) => { - res.end(html_ok); - }); - - await page.goto(`/routing/slashes?port=${port}`); - await page.locator(`a[href="http://localhost:${port}/with-slash/"]`).click(); - expect(await page.content()).toBe(html_ok); - expect(page.url()).toBe(`http://localhost:${port}/with-slash/`); - }); - - test('ignores popstate events from outside the router', async ({ page }) => { - await page.goto('/routing/external-popstate'); - expect(await page.textContent('h1')).toBe('hello'); - - await page.locator('button').click(); - expect(await page.textContent('h1')).toBe('hello'); - - await page.goBack(); - expect(await page.textContent('h1')).toBe('hello'); - - await page.goForward(); - expect(await page.textContent('h1')).toBe('hello'); - }); - - test('recognizes clicks outside the app target', async ({ page }) => { - await page.goto('/routing/link-outside-app-target/source'); - - await page.locator('[href="/routing/link-outside-app-target/target"]').click(); - await expect(page.locator('h1')).toHaveText('target: 1'); - }); - - test('responds to