astro/packages/astro/e2e/view-transitions.test.js
2023-09-12 16:56:23 +00:00

666 lines
21 KiB
JavaScript

import { expect } from '@playwright/test';
import { testFactory } from './test-utils.js';
const test = testFactory({ root: './fixtures/view-transitions/' });
let devServer;
test.beforeAll(async ({ astro }) => {
devServer = await astro.startDevServer();
});
test.afterAll(async () => {
await devServer.stop();
});
function scrollToBottom(page) {
return page.evaluate(() => {
window.scrollY = document.documentElement.scrollHeight;
window.dispatchEvent(new Event('scroll'));
});
}
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());
});
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 2
await page.click('#click-two');
p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
expect(loads.length, 'There should only be 1 page load').toEqual(1);
});
test('Back button is captured', async ({ page, astro }) => {
const loads = [];
page.addListener('load', (p) => {
loads.push(p.title());
});
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 2
await page.click('#click-two');
p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
// Back to page 1
await page.goBack();
p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
expect(loads.length, 'There should only be 1 page load').toEqual(1);
});
test('Clicking on a link with nested content', async ({ page, astro }) => {
const loads = [];
page.addListener('load', (p) => {
loads.push(p.title());
});
// Go to page 4
await page.goto(astro.resolveUrl('/four'));
let p = page.locator('#four');
await expect(p, 'should have content').toHaveText('Page 4');
// Go to page 1
await page.click('#click-one');
p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
expect(loads.length, 'There should only be 1 page load').toEqual(1);
});
test('Moving to a page without ViewTransitions triggers a full page navigation', async ({
page,
astro,
}) => {
const loads = [];
page.addListener('load', (p) => {
loads.push(p.title());
});
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// Go to page 3 which does *not* have ViewTransitions enabled
await page.click('#click-three');
p = page.locator('#three');
await expect(p, 'should have content').toHaveText('Page 3');
expect(
loads.length,
'There should be 2 page loads. The original, then going from 3 to 2'
).toEqual(2);
});
test('Moving within a page without ViewTransitions does not trigger a full page navigation', async ({
page,
astro,
}) => {
const loads = [];
page.addListener('load', async (p) => {
loads.push(p.title());
});
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// Go to page 3 which does *not* have ViewTransitions enabled
await page.click('#click-three');
p = page.locator('#three');
await expect(p, 'should have content').toHaveText('Page 3');
// click a hash link to navigate further down the page
await page.click('#click-hash');
// still on page 3
p = page.locator('#three');
await expect(p, 'should have content').toHaveText('Page 3');
// check that we are further down the page
const Y = await page.evaluate(() => window.scrollY);
expect(Y, 'The target is further down the page').toBeGreaterThan(0);
expect(
loads.length,
'There should be only 2 page loads (for page one & three), but no additional loads for the hash change'
).toEqual(2);
});
test('Moving from a page without ViewTransitions w/ back button', async ({ page, astro }) => {
const loads = [];
page.addListener('load', (p) => {
loads.push(p.title());
});
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// Go to page 3 which does *not* have ViewTransitions enabled
await page.click('#click-three');
p = page.locator('#three');
await expect(p, 'should have content').toHaveText('Page 3');
// Back to page 1
await page.goBack();
p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
});
test('Stylesheets in the head are waited on', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// Go to page 2
await page.click('#click-two');
p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
await expect(p, 'imported CSS updated').toHaveCSS('font-size', '24px');
});
test('astro:page-load event fires when navigating to new page', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
const p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 2
await page.click('#click-two');
const article = page.locator('#twoarticle');
await expect(article, 'should have script content').toHaveText('works');
});
test('astro:page-load event fires when navigating directly to a page', async ({
page,
astro,
}) => {
// Go to page 2
await page.goto(astro.resolveUrl('/two'));
const article = page.locator('#twoarticle');
await expect(article, 'should have script content').toHaveText('works');
});
test('astro:after-swap event fires right after the swap', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 2
await page.click('#click-two');
p = page.locator('#two');
const h = page.locator('html');
await expect(h, 'imported CSS updated').toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
});
test('click hash links does not do navigation', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
const p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// Clicking 1 stays put
await page.click('#click-one');
await expect(p, 'should have content').toHaveText('Page 1');
});
test('click self link (w/o hash) does not do navigation', async ({ page, astro }) => {
const loads = [];
page.addListener('load', (p) => {
loads.push(p.title());
});
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
const p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// Clicking href="" stays on page
await page.click('#click-self');
await expect(p, 'should have content').toHaveText('Page 1');
expect(loads.length, 'There should only be 1 page load').toEqual(1);
});
test('Scroll position restored on back button', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/long-page'));
let article = page.locator('#longpage');
await expect(article, 'should have script content').toBeVisible('exists');
await scrollToBottom(page);
const oldScrollY = await page.evaluate(() => window.scrollY);
// go to page long-page
await page.click('#click-one');
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// Back to page 1
await page.goBack();
const newScrollY = await page.evaluate(() => window.scrollY);
expect(oldScrollY).toEqual(newScrollY);
});
test('Fragment scroll position restored on back button', async ({ page, astro }) => {
// Go to the long page
await page.goto(astro.resolveUrl('/long-page'));
let locator = page.locator('#longpage');
await expect(locator).toBeInViewport();
// Scroll down to middle fragment
await page.click('#click-scroll-down');
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();
// Scroll up to top fragment
await page.click('#click-scroll-up');
locator = page.locator('#longpage');
await expect(locator).toBeInViewport();
// Back to middle of the page
await page.goBack();
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();
});
test('Scroll position restored when transitioning back to fragment', async ({ page, astro }) => {
// Go to the long page
await page.goto(astro.resolveUrl('/long-page'));
let locator = page.locator('#longpage');
await expect(locator).toBeInViewport();
// Scroll down to middle fragment
await page.click('#click-scroll-down');
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();
// Scroll up to top fragment
await page.click('#click-one-again');
locator = page.locator('#one');
await expect(locator).toHaveText('Page 1');
// Back to middle of the page
await page.goBack();
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();
});
test('Scroll position restored on forward button', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page long-page
await page.click('#click-longpage');
let article = page.locator('#longpage');
await expect(article, 'should have script content').toBeVisible('exists');
await scrollToBottom(page);
const oldScrollY = await page.evaluate(() => window.scrollY);
// Back to page 1
await page.goBack();
// Go forward
await page.goForward();
article = page.locator('#longpage');
await expect(article, 'should have script content').toBeVisible('exists');
const newScrollY = await page.evaluate(() => window.scrollY);
expect(oldScrollY).toEqual(newScrollY);
});
test('Fragment scroll position restored on forward button', async ({ page, astro }) => {
// Go to the long page
await page.goto(astro.resolveUrl('/long-page'));
let locator = page.locator('#longpage');
await expect(locator).toBeInViewport();
// Scroll down to middle fragment
await page.click('#click-scroll-down');
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();
// Scroll back to top
await page.goBack();
locator = page.locator('#longpage');
await expect(locator).toBeInViewport();
// Forward to middle of page
await page.goForward();
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();
});
test('<Image /> component forwards transitions to the <img>', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/image-one'));
const img = page.locator('img[data-astro-transition-scope]');
await expect(img).toBeVisible('The image tag should have the transition scope attribute.');
});
test('<video> can persist using transition:persist', async ({ page, astro }) => {
const getTime = () => document.querySelector('video').currentTime;
// Go to page 1
await page.goto(astro.resolveUrl('/video-one'));
const vid = page.locator('video');
await expect(vid).toBeVisible();
const firstTime = await page.evaluate(getTime);
// Navigate to page 2
await page.click('#click-two');
const p = page.locator('#video-two');
await expect(p).toBeVisible();
const secondTime = await page.evaluate(getTime);
expect(secondTime).toBeGreaterThanOrEqual(firstTime);
});
test('Islands can persist using transition:persist', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/island-one'));
let cnt = page.locator('.counter pre');
await expect(cnt).toHaveText('5');
await page.click('.increment');
await expect(cnt).toHaveText('6');
// Navigate to page 2
await page.click('#click-two');
const p = page.locator('#island-two');
await expect(p).toBeVisible();
cnt = page.locator('.counter pre');
// Count should remain
await expect(cnt).toHaveText('6');
});
test('Scripts are only executed once', async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
const p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 2
await page.click('#click-two');
const article = page.locator('#twoarticle');
await expect(article, 'should have script content').toHaveText('works');
const meta = page.locator('[name="script-executions"]');
await expect(meta).toHaveAttribute('content', '0');
});
test('Navigating to the same path but with different query params should result in transition', async ({
page,
astro,
}) => {
const loads = [];
page.addListener('load', (p) => {
loads.push(p.title());
});
// Go to page 1
await page.goto(astro.resolveUrl('/query'));
let p = page.locator('#query-page');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 2
await page.click('#click-two');
p = page.locator('#query-page');
await expect(p, 'should have content').toHaveText('Page 2');
await expect(loads.length, 'There should only be 1 page load').toEqual(1);
});
test('Importing ViewTransitions w/o using the component must not mess with history', async ({
page,
astro,
}) => {
const loads = [];
page.addListener('load', async (p) => {
loads.push(p);
});
// Go to the half bakeed page
await page.goto(astro.resolveUrl('/half-baked'));
let p = page.locator('#half-baked');
await expect(p, 'should have content').toHaveText('Half Baked');
// click a hash link to navigate further down the page
await page.click('#click-hash');
// still on page
p = page.locator('#half-baked');
await expect(p, 'should have content').toHaveText('Half Baked');
// go back within same page without reloading
await page.goBack();
p = page.locator('#half-baked');
await expect(p, 'should have content').toHaveText('Half Baked');
expect(
loads.length,
'There should be only 1 page load. No additional loads for going back on same page'
).toEqual(1);
});
test('Navigation also swaps the attributes of the document root', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/some-attributes'));
let p = page.locator('#heading');
await expect(p, 'should have content').toHaveText('Page with some attributes');
let h = page.locator('html');
await expect(h, 'should have content').toHaveAttribute('lang', 'en');
await page.click('#click-other-attributes');
p = page.locator('#heading');
await expect(p, 'should have content').toHaveText('Page with other attributes');
h = page.locator('html');
await expect(h, 'should have content').toHaveAttribute('lang', 'es');
await expect(h, 'should have content').toHaveAttribute('style', 'background-color: green');
await expect(h, 'should have content').toHaveAttribute('data-other-name', 'value');
await expect(h, 'should have content').toHaveAttribute('data-astro-fake', 'value');
await expect(h, 'should have content').toHaveAttribute('data-astro-transition', 'forward');
await expect(h, 'should be absent').not.toHaveAttribute('class', /.*/);
});
test('Link with data-astro-reload attribute should trigger page load, no tranistion', async ({
page,
astro,
}) => {
const loads = [];
page.addListener('load', (p) => {
loads.push(p.title());
});
// Go to page 4
await page.goto(astro.resolveUrl('/four'));
let p = page.locator('#four');
await expect(p, 'should have content').toHaveText('Page 4');
// go to page 2
await page.click('#click-two');
p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
expect(loads.length, 'There should be 2 page load').toEqual(2);
});
test('Link with download attribute should trigger download, no transition', async ({
page,
astro,
}) => {
// Go to page 4
await page.goto(astro.resolveUrl('/four'));
let p = page.locator('#four');
await expect(p, 'should have content').toHaveText('Page 4');
// Start waiting for download before clicking. Note no await.
const downloadPromise = page.waitForEvent('download', { timeout: 4000 });
await page.click('#click-logo');
await downloadPromise;
});
test('Scroll position is restored on back navigation from page w/o ViewTransitions', async ({
page,
astro,
}) => {
// Go to middle of long page
await page.goto(astro.resolveUrl('/long-page#click-external'));
let locator = page.locator('#click-external');
await expect(locator).toBeInViewport();
// Go to a page that has not enabled ViewTransistions
await page.click('#click-external');
locator = page.locator('#three');
await expect(locator).toHaveText('Page 3');
// Scroll back to long page
await page.goBack();
locator = page.locator('#click-external');
await expect(locator).toBeInViewport();
});
test("Non transition navigation doesn't loose handlers", async ({ page, astro }) => {
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 3
await page.click('#click-three');
p = page.locator('#three');
await expect(p, 'should have content').toHaveText('Page 3');
// go to page 5
await page.click('#click-five');
p = page.locator('#five');
await expect(p, 'should have content').toHaveText('Page 5');
await page.goBack();
p = page.locator('#three');
await expect(p, 'should have content').toHaveText('Page 3');
await page.goBack();
p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
});
test('Moving to a page which redirects to another', async ({ page, astro }) => {
const loads = [];
page.addListener('load', (p) => {
loads.push(p.title());
});
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to page 2
await page.click('#click-redirect-two');
p = page.locator('#two');
await expect(p, 'should have content').toHaveText('Page 2');
// go back
await page.goBack();
p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
expect(
loads.length,
'There should only be the initial page load and two normal transitions'
).toEqual(1);
});
test('Redirect to external site causes page load', async ({ page, astro }) => {
const loads = [];
page.addListener('load', (p) => {
loads.push(p.title());
});
// Go to page 1
await page.goto(astro.resolveUrl('/one'));
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
// go to external page
await page.click('#click-redirect-external');
// doesn't work for playwright when we are too fast
await page.waitForTimeout(1000);
p = page.locator('h1');
await expect(p, 'should have content').toBeVisible();
expect(loads.length, 'There should be 2 page loads').toEqual(2);
});
test('client:only styles are retained on transition', async ({ page, astro }) => {
const totalExpectedStyles = 8;
// Go to page 1
await page.goto(astro.resolveUrl('/client-only-one'));
let msg = page.locator('.counter-message');
await expect(msg).toHaveText('message here');
let styles = await page.locator('style').all();
expect(styles.length).toEqual(totalExpectedStyles);
await page.click('#click-two');
let pageTwo = page.locator('#page-two');
await expect(pageTwo, 'should have content').toHaveText('Page 2');
styles = await page.locator('style').all();
expect(styles.length).toEqual(totalExpectedStyles, 'style count has not changed');
});
test('Horizontal scroll position restored on back button', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/wide-page'));
let article = page.locator('#widepage');
await expect(article, 'should have script content').toBeVisible('exists');
let locator = page.locator('#click-one');
await expect(locator).not.toBeInViewport();
await page.click('#click-right');
locator = page.locator('#click-one');
await expect(locator).toBeInViewport();
locator = page.locator('#click-top');
await expect(locator).toBeInViewport();
await page.click('#click-one');
let p = page.locator('#one');
await expect(p, 'should have content').toHaveText('Page 1');
await page.goBack();
locator = page.locator('#click-one');
await expect(locator).toBeInViewport();
locator = page.locator('#click-top');
await expect(locator).toBeInViewport();
await page.click('#click-top');
locator = page.locator('#click-one');
await expect(locator).not.toBeInViewport();
});
});