diff --git a/packages/astro/e2e/fixtures/view-transitions/src/pages/abort2.astro b/packages/astro/e2e/fixtures/view-transitions/src/pages/abort2.astro index f4f118db8e..4d4f9e934e 100644 --- a/packages/astro/e2e/fixtures/view-transitions/src/pages/abort2.astro +++ b/packages/astro/e2e/fixtures/view-transitions/src/pages/abort2.astro @@ -6,7 +6,7 @@ import { ViewTransitions, fade } from 'astro:transitions'; -

Abort

+

Abort

@@ -15,11 +15,10 @@ import { ViewTransitions, fade } from 'astro:transitions'; import {navigate } from 'astro:transitions/client'; setTimeout(()=>{ - [...document.getAnimations()].forEach((a) => a.addEventListener('cancel', - (e) => console.log("[test]",e.type, a.animationName))); - console.log("[test] navigate to /one") + [...document.getAnimations()].forEach((a) => a.addEventListener('cancel', (e) => console.log("[test]", e.type, a.animationName))); + console.log("[test] navigate to /one"); navigate("/one"); - }, 1000); + }, 200); console.log('[test] navigate to "."') navigate("/abort2"); diff --git a/packages/astro/e2e/view-transitions.test.js b/packages/astro/e2e/view-transitions.test.js index 3fb610affb..812c4182af 100644 --- a/packages/astro/e2e/view-transitions.test.js +++ b/packages/astro/e2e/view-transitions.test.js @@ -13,6 +13,14 @@ test.afterAll(async () => { await devServer.stop(); }); +function collectLoads(page) { + const loads = []; + page.on('load', async () => { + const url = page.url(); + if (url !== 'about:blank') loads.push(await page.title()); + }); + return loads; +} function scrollToBottom(page) { return page.evaluate(() => { window.scrollY = document.documentElement.scrollHeight; @@ -34,12 +42,13 @@ function collectPreloads(page) { }); } +async function nativeViewTransition(page) { + return page.evaluate(() => document.startViewTransition !== undefined) +} + test.describe('View Transitions', () => { test('Moving from page 1 to page 2', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); // Go to page 1 await page.goto(astro.resolveUrl('/one')); @@ -55,10 +64,7 @@ test.describe('View Transitions', () => { }); test('Back button is captured', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); // Go to page 1 await page.goto(astro.resolveUrl('/one')); @@ -79,11 +85,7 @@ test.describe('View Transitions', () => { }); test('Clicking on a link with nested content', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); - + const loads = collectLoads(page); // Go to page 4 await page.goto(astro.resolveUrl('/four')); let p = page.locator('#four'); @@ -98,11 +100,7 @@ test.describe('View Transitions', () => { }); test('Clicking on a link to a page with non-recommended headers', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); - + const loads = collectLoads(page); // Go to page 4 await page.goto(astro.resolveUrl('/one')); let p = page.locator('#one'); @@ -120,10 +118,7 @@ test.describe('View Transitions', () => { page, astro, }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); // Go to page 1 await page.goto(astro.resolveUrl('/one')); @@ -145,10 +140,7 @@ test.describe('View Transitions', () => { page, astro, }) => { - const loads = []; - page.addListener('load', async (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); // Go to page 1 await page.goto(astro.resolveUrl('/one')); let p = page.locator('#one'); @@ -176,10 +168,7 @@ test.describe('View Transitions', () => { }); test('Moving from a page without ViewTransitions w/ back button', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); // Go to page 1 await page.goto(astro.resolveUrl('/one')); @@ -250,7 +239,7 @@ test.describe('View Transitions', () => { }); test('No page rendering during swap()', async ({ page, astro }) => { - // This has been a problem with theme switchers (e.g. for drakmode) + // This has been a problem with theme switchers (e.g. for darkmode) // Swap() should not trigger any page renders and give users the chance to // correct attributes in the astro:after-swap handler before they become visible @@ -258,6 +247,10 @@ test.describe('View Transitions', () => { // The test succeeds if no additional animation beside those of the // view transition is triggered during swap() + // Only works for browsers with native view transitions + if (!await nativeViewTransition(page)) return; + + await page.goto(astro.resolveUrl('/listener-one')); let p = page.locator('#totwo'); await expect(p, 'should have content').toHaveText('Go to listener two'); @@ -295,10 +288,8 @@ test.describe('View Transitions', () => { }); test('click self link (w/o hash) does not do navigation', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + // Go to page 1 await page.goto(astro.resolveUrl('/one')); const p = page.locator('#one'); @@ -596,10 +587,8 @@ test.describe('View Transitions', () => { page, astro, }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + // Go to page 1 await page.goto(astro.resolveUrl('/query')); @@ -618,10 +607,8 @@ test.describe('View Transitions', () => { page, astro, }) => { - const loads = []; - page.addListener('load', async (p) => { - loads.push(p); - }); + const loads = collectLoads(page); + // Go to the half bakeed page await page.goto(astro.resolveUrl('/half-baked')); let p = page.locator('#half-baked'); @@ -669,10 +656,8 @@ test.describe('View Transitions', () => { page, astro, }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + // Go to page 4 await page.goto(astro.resolveUrl('/four')); @@ -684,6 +669,9 @@ test.describe('View Transitions', () => { p = page.locator('#two'); await expect(p, 'should have content').toHaveText('Page 2'); + // go to next page + await page.click('#click-longpage'); + expect(loads.length, 'There should be 2 page load').toEqual(2); }); @@ -703,10 +691,8 @@ test.describe('View Transitions', () => { }); test('data-astro-reload not required for non-html content', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + // Go to page 4 await page.goto(astro.resolveUrl('/four')); let p = page.locator('#four'); @@ -765,10 +751,8 @@ test.describe('View Transitions', () => { }); test('Moving to a page which redirects to another', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + // Go to page 1 await page.goto(astro.resolveUrl('/one')); @@ -792,10 +776,8 @@ test.describe('View Transitions', () => { }); test('Redirect to external site causes page load', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + // Go to page 1 await page.goto(astro.resolveUrl('/one')); @@ -805,11 +787,9 @@ test.describe('View Transitions', () => { // go to external page await page.click('#click-redirect-external'); // doesn't work for playwright when we are too fast - p = page.locator('h1'); - await expect(p, 'should have content').toBeVisible(); await page.waitForURL('http://example.com'); - await page.waitForFunction((arr) => arr.length === 2, loads); + await expect(page.locator("h1"), 'should have content').toHaveText('Example Domain'); expect(loads.length, 'There should be 2 page loads').toEqual(2); }); @@ -1019,10 +999,8 @@ test.describe('View Transitions', () => { }); test('form POST that redirects to another page is handled', async ({ page, astro }) => { - const loads = []; - page.addListener('load', async (p) => { - loads.push(p); - }); + const loads = collectLoads(page); + await page.goto(astro.resolveUrl('/form-one')); @@ -1048,10 +1026,8 @@ test.describe('View Transitions', () => { }); test('form GET that redirects to another page is handled', async ({ page, astro }) => { - const loads = []; - page.addListener('load', async (p) => { - loads.push(p); - }); + const loads = collectLoads(page); + await page.goto(astro.resolveUrl('/form-one?method=get')); @@ -1070,10 +1046,8 @@ test.describe('View Transitions', () => { }); test('form POST when there is an error shows the error', async ({ page, astro }) => { - const loads = []; - page.addListener('load', async (p) => { - loads.push(p); - }); + const loads = collectLoads(page); + await page.goto(astro.resolveUrl('/form-one?throw')); @@ -1095,11 +1069,8 @@ test.describe('View Transitions', () => { page, astro, }) => { - const loads = []; + const loads = collectLoads(page); - page.addListener('load', async (p) => { - loads.push(p); - }); const postedEncodings = []; @@ -1130,11 +1101,8 @@ test.describe('View Transitions', () => { }); test('form POST respects enctype attribute', async ({ page, astro }) => { - const loads = []; + const loads = collectLoads(page); - page.addListener('load', async (p) => { - loads.push(p); - }); const postedEncodings = []; @@ -1222,10 +1190,8 @@ test.describe('View Transitions', () => { }); test('form POST with no action handler', async ({ page, astro }) => { - const loads = []; - page.addListener('load', async (p) => { - loads.push(p); - }); + const loads = collectLoads(page); + await page.goto(astro.resolveUrl('/form-two')); @@ -1256,10 +1222,8 @@ test.describe('View Transitions', () => { }); test('click on an svg anchor should trigger navigation', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + await page.goto(astro.resolveUrl('/non-html-anchor')); let locator = page.locator('#insidesvga'); @@ -1271,10 +1235,8 @@ test.describe('View Transitions', () => { }); test('click inside an svg anchor should trigger navigation', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + await page.goto(astro.resolveUrl('/non-html-anchor')); let locator = page.locator('#insidesvga'); await expect(locator, 'should have content').toHaveText('text within a svga'); @@ -1285,10 +1247,8 @@ test.describe('View Transitions', () => { }); test('click on an area in an image map should trigger navigation', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + await page.goto(astro.resolveUrl('/non-html-anchor')); let locator = page.locator('#area'); await expect(locator, 'should have attribute').toHaveAttribute('shape', 'default'); @@ -1328,10 +1288,8 @@ test.describe('View Transitions', () => { }); test('view transition should also work with 404 page', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + // Go to page 1 await page.goto(astro.resolveUrl('/one')); @@ -1347,10 +1305,8 @@ test.describe('View Transitions', () => { }); test('custom elements can trigger a view transition', async ({ page, astro }) => { - const loads = []; - page.addListener('load', (p) => { - loads.push(p.title()); - }); + const loads = collectLoads(page); + await page.goto(astro.resolveUrl('/one')); await expect(page.locator('#one'), 'should have content').toHaveText('Page 1'); // go to page 2 @@ -1361,6 +1317,9 @@ test.describe('View Transitions', () => { }); test('transition:name should be escaped correctly', async ({ page, astro }) => { + + // view-transition-name errors on browser w/o native support + if (!await nativeViewTransition(page)) return; const expectedAnimations = new Set(); const checkName = async (selector, name) => { expectedAnimations.add(name); @@ -1518,7 +1477,6 @@ test.describe('View Transitions', () => { // clicks on slow loading page two // after short delay clicks on fast loading page one // even after some delay /two should not show up - await new Promise((resolve) => setTimeout(resolve, 2000)); // wait is part of the test let p = page.locator('#one'); await expect(p, 'should have content').toHaveText('Page 1'); }); @@ -1533,16 +1491,19 @@ test.describe('View Transitions', () => { // Navigate to self with a 10 second animation // shortly after starting that, change your mind an navigate to /one // check that animations got canceled - await new Promise((resolve) => setTimeout(resolve, 1000)); // wait is part of the test let p = page.locator('#one'); await expect(p, 'should have content').toHaveText('Page 1'); - // This test would be more important for a browser without native view transitions // as those do not have automatic cancelation of transitions. - // For simulated view transitions, the last line would be missing as enter and exit animations - // don't run in parallel. - expect(lines.join('\n')).toBe( - '[test] navigate to "."\n[test] navigate to /one\n[test] cancel astroFadeOut\n[test] cancel astroFadeIn' - ); + // For simulated view transitions, the last line would be missing + // as enter and exit animations don't run in parallel. + + let expected = '[test] navigate to "."\n[test] navigate to /one\n[test] cancel astroFadeOut'; + const native = await nativeViewTransition(page); + if (native) { + expected += '\n[test] cancel astroFadeIn'; + } + await page.click('#click-two'); + expect(lines.join('\n')).toBe(expected); }); }); diff --git a/packages/astro/playwright.firefox.config.js b/packages/astro/playwright.firefox.config.js index d8ec35031d..537bb4099e 100644 --- a/packages/astro/playwright.firefox.config.js +++ b/packages/astro/playwright.firefox.config.js @@ -5,7 +5,7 @@ process.stdout.isTTY = false; const config = { // TODO: add more tests like view transitions and audits, and fix them. Some of them are failing. - testMatch: ['e2e/css.test.js', 'e2e/prefetch.test.js'], + testMatch: ['e2e/css.test.js', 'e2e/prefetch.test.js', 'e2e/view-transitions.test.js'], /* Maximum time one test can run for. */ timeout: 40 * 1000, expect: {