test: migrate spa preloader tests to playwright (#31273)

This commit is contained in:
Francesco Agnoletto 2025-03-17 23:48:38 +01:00 committed by GitHub
parent 1f2b0b2f4e
commit c91505a3f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 193 additions and 127 deletions

View File

@ -0,0 +1,95 @@
import { fileURLToPath } from 'node:url'
import { isWindows } from 'std-env'
import { join } from 'pathe'
import type { Page } from 'playwright-core'
import { waitForHydration } from '@nuxt/test-utils'
import { expect, test } from './test-utils'
/**
* This test suite verifies that the SPA loading template is correctly rendered
* outside the app tag when spaLoadingTemplateLocation is set to 'body'.
*/
const isWebpack = process.env.TEST_BUILDER === 'webpack' || process.env.TEST_BUILDER === 'rspack'
const isDev = process.env.TEST_ENV === 'dev'
const fixtureDir = fileURLToPath(new URL('../fixtures/spa-loader', import.meta.url))
// Skip tests in dev mode
test.skip(isDev, 'These tests are only relevant in production mode')
const loaderHTML = '<div id="__nuxt"></div><div id="__nuxt-loader"><div data-testid="loader">loading...</div></div>'
test.use({
nuxt: {
rootDir: fixtureDir,
server: true,
browser: true,
setupTimeout: (isWindows ? 360 : 120) * 1000,
nuxtConfig: {
buildDir: isDev ? join(fixtureDir, '.nuxt', 'test', Math.random().toString(36).slice(2, 8)) : undefined,
builder: isWebpack ? 'webpack' : 'vite',
spaLoadingTemplate: true,
experimental: {
spaLoadingTemplateLocation: 'body',
},
},
},
})
test.describe('spaLoadingTemplateLocation flag is set to `body`', () => {
test('should render loader alongside appTag', async ({ request }) => {
const response = await request.get('/spa')
const html = await response.text()
expect(html).toContain(loaderHTML)
})
test('should render spa-loader', async ({ page, fetch }) => {
expect(await fetch('/spa').then(r => r.text())).toContain(loaderHTML)
// Navigate to the SPA page
await page.goto('/spa')
// Verify the loader is visible first and content is hidden
expect(await getState(page)).toEqual({
loader: true,
content: false,
})
page.dispatchEvent('html', 'finishHydration')
await waitForHydration(page, '/spa', 'hydration')
expect(await getState(page)).toEqual({
loader: false,
content: true,
})
})
test('should render content without spa-loader for SSR pages', async ({ page, fetch }) => {
expect(await fetch('/ssr').then(r => r.text())).not.toContain(loaderHTML)
// Navigate to SSR page
await page.goto('/ssr')
// Verify the loader is hidden and content is visible for SSR pages
expect(await getState(page)).toEqual({
loader: false,
content: true,
})
})
})
// isVisible is preferred here as we want to snapshot the state of the page at a specific moment, since waiting would make this test flake.
// https://github.com/nuxt/nuxt/pull/31273#issuecomment-2731002417
async function getState (page: Page) {
const [loader, content] = await Promise.all([
page.getByTestId('loader').isVisible(),
page.getByTestId('content').isVisible(),
])
const state = {
loader,
content,
}
return state
}

View File

@ -0,0 +1,95 @@
import { fileURLToPath } from 'node:url'
import { isWindows } from 'std-env'
import { join } from 'pathe'
import type { Page } from 'playwright-core'
import { waitForHydration } from '@nuxt/test-utils'
import { expect, test } from './test-utils'
/**
* This test suite verifies that the SPA loading template is correctly rendered
* inside the app tag when spaLoadingTemplateLocation is set to 'within'.
*/
const isWebpack = process.env.TEST_BUILDER === 'webpack' || process.env.TEST_BUILDER === 'rspack'
const isDev = process.env.TEST_ENV === 'dev'
const fixtureDir = fileURLToPath(new URL('../fixtures/spa-loader', import.meta.url))
// Skip tests in dev mode
test.skip(isDev, 'These tests are only relevant in production mode')
const loaderHTML = '<div id="__nuxt"><div data-testid="loader">loading...</div></div>'
test.use({
nuxt: {
rootDir: fixtureDir,
dev: isDev,
server: true,
browser: true,
setupTimeout: (isWindows ? 360 : 120) * 1000,
nuxtConfig: {
buildDir: isDev ? join(fixtureDir, '.nuxt', 'test', Math.random().toString(36).slice(2, 8)) : undefined,
builder: isWebpack ? 'webpack' : 'vite',
spaLoadingTemplate: true,
experimental: {
spaLoadingTemplateLocation: 'within',
},
},
},
})
test.describe('spaLoadingTemplateLocation flag is set to `within`', () => {
test('should render loader inside appTag', async ({ request }) => {
const response = await request.get('/spa')
const html = await response.text()
expect(html).toContain(loaderHTML)
})
test('spa-loader does not appear while the app is mounting', async ({ page }) => {
// Navigate to the SPA page
await page.goto('/spa')
// wait for intervening (less optimal!) current behaviour
await expect(page.getByTestId('loader')).toBeHidden()
await expect(page.getByTestId('content')).toBeHidden()
expect(await getState(page)).toEqual({
loader: false,
content: false,
})
page.dispatchEvent('html', 'finishHydration')
await waitForHydration(page, '/spa', 'hydration')
// Wait for content to become visible after hydration
await expect(page.getByTestId('content')).toBeVisible()
})
test('should render content without spa-loader for SSR pages', async ({ page, fetch }) => {
expect(await fetch('/ssr').then(r => r.text())).not.toContain(loaderHTML)
// Navigate to SSR page
await page.goto('/ssr')
// Verify the loader is hidden and content is visible for SSR pages
expect(await getState(page)).toEqual({
loader: false,
content: true,
})
})
})
// isVisible is preferred here as we want to snapshot the state of the page at a specific moment, since waiting that would make this test flake.
// https://github.com/nuxt/nuxt/pull/31273#issuecomment-2731002417
async function getState (page: Page) {
const [loader, content] = await Promise.all([
page.getByTestId('loader').isVisible(),
page.getByTestId('content').isVisible(),
])
const state = {
loader,
content,
}
return state
}

View File

@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
if (import.meta.client) { if (import.meta.client) {
await new Promise(resolve => setTimeout(resolve, 50)) await new Promise<void>((resolve) => {
document.addEventListener('finishHydration', () => resolve())
})
} }
</script> </script>
@ -9,7 +11,3 @@ if (import.meta.client) {
app content app content
</div> </div>
</template> </template>
<style scoped>
</style>

View File

@ -1,3 +0,0 @@
export default defineNuxtPlugin(async () => {
await new Promise(resolve => setTimeout(resolve, 50))
})

View File

@ -1,54 +0,0 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { isWindows } from 'std-env'
import { $fetch, createPage, setup, url } from '@nuxt/test-utils/e2e'
import { join } from 'pathe'
const isWebpack =
process.env.TEST_BUILDER === 'webpack' ||
process.env.TEST_BUILDER === 'rspack'
const isDev = process.env.TEST_ENV === 'dev'
const fixtureDir = fileURLToPath(new URL('../fixtures/spa-loader', import.meta.url))
if (!isDev) {
await setup({
rootDir: fixtureDir,
dev: isDev,
server: true,
browser: true,
setupTimeout: (isWindows ? 360 : 120) * 1000,
nuxtConfig: {
buildDir: isDev ? join(fixtureDir, '.nuxt', 'test', Math.random().toString(36).slice(2, 8)) : undefined,
builder: isWebpack ? 'webpack' : 'vite',
spaLoadingTemplate: true,
experimental: {
spaLoadingTemplateLocation: 'within',
},
},
})
}
describe.skipIf(isDev)('spaLoadingTemplateLocation flag is set to `within`', () => {
it('should render loader inside appTag', async () => {
const html = await $fetch<string>('/spa')
expect(html).toContain(`<div id="__nuxt"><div data-testid="loader">loading...</div></div>`)
})
it('spa-loader does not appear while the app is mounting', async () => {
const page = await createPage()
await page.goto(url('/spa'))
await page.getByTestId('loader').waitFor({ state: 'visible' })
expect(await page.getByTestId('content').isHidden()).toBeTruthy()
await page.waitForFunction(() => window.useNuxtApp?.() && window.useNuxtApp?.().isHydrating)
expect(await page.getByTestId('content').isHidden()).toBeTruthy()
await page.getByTestId('content').waitFor({ state: 'visible' })
await page.close()
}, 60_000)
})

View File

@ -1,65 +0,0 @@
import { fileURLToPath } from 'node:url'
import { describe, expect, it } from 'vitest'
import { isWindows } from 'std-env'
import { createPage, setup, url } from '@nuxt/test-utils/e2e'
import type { Page } from 'playwright-core'
import { join } from 'pathe'
const isWebpack = process.env.TEST_BUILDER === 'webpack' || process.env.TEST_BUILDER === 'rspack'
const isDev = process.env.TEST_ENV === 'dev'
const fixtureDir = fileURLToPath(new URL('../fixtures/spa-loader', import.meta.url))
if (!isDev) {
await setup({
rootDir: fixtureDir,
server: true,
browser: true,
setupTimeout: (isWindows ? 360 : 120) * 1000,
nuxtConfig: {
buildDir: isDev ? join(fixtureDir, '.nuxt', 'test', Math.random().toString(36).slice(2, 8)) : undefined,
builder: isWebpack ? 'webpack' : 'vite',
spaLoadingTemplate: true,
experimental: {
spaLoadingTemplateLocation: 'body',
},
},
})
}
describe.skipIf(isDev)('spaLoadingTemplateLocation flag is set to `body`', () => {
it('should render spa-loader', async () => {
const page = await createPage()
await page.goto(url('/spa'), { waitUntil: 'domcontentloaded' })
await page.getByTestId('loader').waitFor({ state: 'visible' })
expect(await page.getByTestId('content').isHidden()).toBeTruthy()
await page.getByTestId('content').waitFor({ state: 'visible' })
expect(await page.getByTestId('loader').isHidden()).toBeTruthy()
await page.close()
}, 60_000)
it('should render content without spa-loader', async () => {
const page = await createPage()
await page.goto(url('/ssr'), { waitUntil: 'domcontentloaded' })
const [loaderIsHidden, contentIsHidden] = await getState(page)
expect(loaderIsHidden).toBeTruthy()
expect(contentIsHidden).toBeFalsy()
await page.close()
}, 60_000)
})
function getState (page: Page) {
const loader = page.getByTestId('loader')
const content = page.getByTestId('content')
return Promise.all([
loader.isHidden(),
content.isHidden(),
])
}