diff --git a/test/basic.test.ts b/test/basic.test.ts index 1051bc86ce..2b97b919cd 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -8,7 +8,7 @@ import { $fetch, createPage, fetch, isDev, setup, startServer, url, useTestConte import { $fetchComponent } from '@nuxt/test-utils/experimental' import type { NuxtIslandResponse } from '../packages/nuxt/src/core/runtime/nitro/renderer' -import { expectNoClientErrors, expectWithPolling, isRenderingJson, parseData, parsePayload, renderPage, withLogs } from './utils' +import { expectNoClientErrors, expectWithPolling, gotoPath, isRenderingJson, parseData, parsePayload, renderPage } from './utils' const isWebpack = process.env.TEST_BUILDER === 'webpack' @@ -17,7 +17,7 @@ await setup({ dev: process.env.TEST_ENV === 'dev', server: true, browser: true, - setupTimeout: (isWindows ? 240 : 120) * 1000, + setupTimeout: (isWindows ? 360 : 120) * 1000, nuxtConfig: { builder: isWebpack ? 'webpack' : 'vite', buildDir: process.env.NITRO_BUILD_DIR, @@ -53,9 +53,8 @@ describe('route rules', () => { }) it('test noScript routeRules', async () => { - const page = await createPage('/no-scripts') - expect(await page.locator('script').all()).toHaveLength(0) - await page.close() + const html = await $fetch('/no-scripts') + expect(html).not.toContain(' { expect(status).toEqual(404) expect(headers.get('Set-Cookie')).toBe('set-in-plugin=true; Path=/') - const page = await createPage('/navigate-to-forbidden') - await page.waitForLoadState('networkidle') + const { page } = await renderPage('/navigate-to-forbidden') + await page.getByText('should throw a 404 error').click() expect(await page.getByRole('heading').textContent()).toMatchInlineSnapshot('"Page Not Found: /forbidden"') - await page.goto(url('/navigate-to-forbidden')) - await page.waitForLoadState('networkidle') + await gotoPath(page, '/navigate-to-forbidden') await page.getByText('should be caught by catchall').click() expect(await page.getByRole('heading').textContent()).toMatchInlineSnapshot('"[...slug].vue"') @@ -172,22 +170,21 @@ describe('pages', () => { it('expect no loading indicator on middleware abortNavigation', async () => { const { page } = await renderPage('/') - await page.waitForLoadState('networkidle') await page.locator('#middleware-abort-non-fatal').click() expect(await page.locator('#lodagin-indicator').all()).toHaveLength(0) await page.locator('#middleware-abort-non-fatal-error').click() expect(await page.locator('#lodagin-indicator').all()).toHaveLength(0) + await page.close() }) it('should render correctly when loaded on a different path', async () => { - const page = await createPage('/proxy') + const { page, pageErrors } = await renderPage('/proxy') - await page.waitForLoadState('networkidle') expect(await page.innerText('body')).toContain('Composable | foo: auto imported from ~/composables/foo.ts') await page.close() - await expectNoClientErrors('/proxy') + expect(pageErrors).toEqual([]) }) it('preserves query', async () => { @@ -265,11 +262,7 @@ describe('pages', () => { // ensure components are not rendered server-side expect(html).not.toContain('Should not be server rendered') - await expectNoClientErrors('/client-only-components') - - const page = await createPage('/client-only-components') - - await page.waitForLoadState('networkidle') + const { page, pageErrors } = await renderPage('/client-only-components') const hiddenSelectors = [ '.string-stateful-should-be-hidden', @@ -331,25 +324,16 @@ describe('pages', () => { await Promise.all(hiddenSelectors.map(selector => page.locator(selector).isVisible())) .then(results => results.forEach(isVisible => expect(isVisible).toBeTruthy())) + expect(pageErrors).toEqual([]) await page.close() }) it('/wrapper-expose/layout', async () => { - await expectNoClientErrors('/wrapper-expose/layout') - - let lastLog: string|undefined - const page = await createPage('/wrapper-expose/layout') - page.on('console', (log) => { - lastLog = log.text() - }) - page.on('pageerror', (log) => { - lastLog = log.message - }) - await page.waitForLoadState('networkidle') + const { page, consoleLogs, pageErrors } = await renderPage('/wrapper-expose/layout') await page.locator('.log-foo').first().click() - expect(lastLog).toContain('.logFoo is not a function') + expect(pageErrors.at(-1)?.toString() || consoleLogs.at(-1)!.text).toContain('.logFoo is not a function') await page.locator('.log-hello').first().click() - expect(lastLog).toContain('world') + expect(consoleLogs.at(-1)!.text).toContain('world') await page.locator('.add-count').first().click() expect(await page.locator('.count').first().innerText()).toContain('1') @@ -357,14 +341,15 @@ describe('pages', () => { await page.locator('.swap-layout').click() await page.waitForFunction(() => document.querySelector('.count')?.innerHTML.includes('0')) await page.locator('.log-foo').first().click() - expect(lastLog).toContain('bar') + expect(consoleLogs.at(-1)!.text).toContain('bar') await page.locator('.log-hello').first().click() - expect(lastLog).toContain('.logHello is not a function') + expect(pageErrors.at(-1)?.toString() || consoleLogs.at(-1)!.text).toContain('.logHello is not a function') await page.locator('.add-count').first().click() await page.waitForFunction(() => document.querySelector('.count')?.innerHTML.includes('1')) // change layout await page.locator('.swap-layout').click() await page.waitForFunction(() => document.querySelector('.count')?.innerHTML.includes('0')) + await page.close() }) it('/client-only-explicit-import', async () => { @@ -379,24 +364,16 @@ describe('pages', () => { }) it('/wrapper-expose/page', async () => { - await expectNoClientErrors('/wrapper-expose/page') - let lastLog: string|undefined - const page = await createPage('/wrapper-expose/page') - page.on('console', (log) => { - lastLog = log.text() - }) - page.on('pageerror', (log) => { - lastLog = log.message - }) - await page.waitForLoadState('networkidle') + const { page, pageErrors, consoleLogs } = await renderPage('/wrapper-expose/page') await page.locator('#log-foo').click() - expect(lastLog === 'bar').toBeTruthy() + expect(consoleLogs.at(-1)?.text).toBe('bar') // change page await page.locator('#to-hello').click() await page.locator('#log-foo').click() - expect(lastLog?.includes('.foo is not a function')).toBeTruthy() + expect(pageErrors.at(-1)?.toString() || consoleLogs.at(-1)!.text).toContain('.foo is not a function') await page.locator('#log-hello').click() - expect(lastLog === 'world').toBeTruthy() + expect(consoleLogs.at(-1)?.text).toBe('world') + await page.close() }) it('client-fallback', async () => { @@ -421,10 +398,7 @@ describe('pages', () => { expect(html).not.toContain('

') expect(html).toContain('hi') - await expectNoClientErrors('/client-fallback') - - const page = await createPage('/client-fallback') - await page.waitForLoadState('networkidle') + const { page, pageErrors } = await renderPage('/client-fallback') // ensure components reactivity once mounted await page.locator('#increment-count').click() expect(await page.locator('#sugar-counter').innerHTML()).toContain('Sugar Counter 12 x 1 = 12') @@ -432,6 +406,7 @@ describe('pages', () => { expect(await page.locator('#keep-fallback').all()).toHaveLength(1) // #20833 expect(await page.locator('body').innerHTML()).not.toContain('Hello world !') + expect(pageErrors).toEqual([]) await page.close() }) @@ -539,16 +514,14 @@ describe('nuxt links', () => { }) it('preserves route state', async () => { - const page = await createPage('/nuxt-link/trailing-slash') - await page.waitForLoadState('networkidle') + const { page } = await renderPage('/nuxt-link/trailing-slash') for (const selector of ['nuxt-link', 'router-link', 'link-with-trailing-slash', 'link-without-trailing-slash']) { await page.locator(`.${selector}[href*=with-state]`).click() - await page.waitForLoadState('networkidle') - expect(await page.getByTestId('window-state').innerText()).toContain('bar') + await page.getByTestId('window-state').getByText('bar').waitFor() await page.locator(`.${selector}[href*=without-state]`).click() - await page.waitForLoadState('networkidle') + await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath.includes('without-state')) expect(await page.getByTestId('window-state').innerText()).not.toContain('bar') } @@ -760,8 +733,8 @@ describe('errors', () => { await page.waitForURL(url('/chunk-error')) expect(consoleLogs.map(c => c.text).join('')).toContain('caught chunk load error') expect(await page.innerText('div')).toContain('Chunk error page') - await page.waitForLoadState('networkidle') - expect(await page.innerText('div')).toContain('State: 3') + await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/chunk-error') + await page.locator('div').getByText('State: 3').waitFor() await page.close() }) @@ -803,8 +776,7 @@ describe('middlewares', () => { it('should allow aborting navigation fatally on client-side', async () => { const html = await $fetch('/middleware-abort') expect(html).not.toContain('This is the error page') - const page = await createPage('/middleware-abort') - await page.waitForLoadState('networkidle') + const { page } = await renderPage('/middleware-abort') expect(await page.innerHTML('body')).toContain('This is the error page') await page.close() }) @@ -901,13 +873,11 @@ describe('composable tree shaking', () => { expect(html).toContain('Tree Shake Example') - const page = await createPage('/tree-shake') - // check page doesn't have any errors or warnings in the console - await page.waitForLoadState('networkidle') + const { page, pageErrors } = await renderPage('/tree-shake') // ensure scoped classes are correctly assigned between client and server expect(await page.$eval('h1', e => getComputedStyle(e).color)).toBe('rgb(255, 192, 203)') - await expectNoClientErrors('/tree-shake') + expect(pageErrors).toEqual([]) await page.close() }) @@ -922,8 +892,8 @@ describe('server tree shaking', () => { expect(html).not.toContain('rendered client-side') expect(html).not.toContain('id="client-side"') - const page = await createPage('/client') - await page.waitForLoadState('networkidle') + const { page } = await renderPage('/client') + await page.waitForFunction(() => window.useNuxtApp?.()) // ensure scoped classes are correctly assigned between client and server expect(await page.$eval('.red', e => getComputedStyle(e).color)).toBe('rgb(255, 0, 0)') expect(await page.$eval('.blue', e => getComputedStyle(e).color)).toBe('rgb(0, 0, 255)') @@ -1017,44 +987,29 @@ describe('extends support', () => { // Bug #7337 describe('deferred app suspense resolve', () => { - async function behaviour (path: string) { - await withLogs(async (page, logs) => { - await page.goto(url(path)) - await page.waitForLoadState('networkidle') + it.each(['/async-parent/child', '/internal-layout/async-parent/child'])('should wait for all suspense instance on initial hydration', async (path) => { + const { page, consoleLogs } = await renderPage(path) - // Wait for all pending micro ticks to be cleared in case hydration hasn't finished yet. - await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) + // Wait for all pending micro ticks to be cleared in case hydration hasn't finished yet. + await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) - const hydrationLogs = logs.filter(log => log.includes('isHydrating')) - expect(hydrationLogs.length).toBe(3) - expect(hydrationLogs.every(log => log === 'isHydrating: true')) - }) - } - it('should wait for all suspense instance on initial hydration', async () => { - await behaviour('/async-parent/child') - }) - it('should wait for all suspense instance on initial hydration', async () => { - await behaviour('/internal-layout/async-parent/child') + const hydrationLogs = consoleLogs.filter(log => log.text.includes('isHydrating')) + expect(hydrationLogs.length).toBe(3) + expect(hydrationLogs.every(log => log.text === 'isHydrating: true')) + + await page.close() }) + it('should wait for suspense in parent layout', async () => { - const page = await createPage('/hydration/layout') - await page.waitForLoadState('networkidle') - - // Wait for all pending micro ticks to be cleared in case hydration hasn't finished yet. - await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) - - const html = await page.getByRole('document').innerHTML() - expect(html).toContain('Tests whether hydration is properly resolved within an async layout') + const { page } = await renderPage('/hydration/layout') + await page.getByText('Tests whether hydration is properly resolved within an async layout').waitFor() + await page.close() }) + it('should fully hydrate even if there is a redirection on a page with `ssr: false`', async () => { - const page = await createPage('/hydration/spa-redirection/start') - await page.waitForLoadState('networkidle') - - // Wait for all pending micro ticks to be cleared in case hydration hasn't finished yet. - await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) - - const html = await page.getByRole('document').innerHTML() - expect(html).toContain('fully hydrated and ready to go') + const { page } = await renderPage('/hydration/spa-redirection/start') + await page.getByText('fully hydrated and ready to go').waitFor() + await page.close() }) }) @@ -1071,14 +1026,7 @@ describe('nested suspense', () => { ]) it.each(navigations)('should navigate from %s to %s with no white flash', async (start, nav) => { - const page = await createPage(start, {}) - const logs: string[] = [] - page.on('console', (msg) => { - const text = msg.text() - if (text.includes('[vite]') || text.includes(' is an experimental feature')) { return } - logs.push(msg.text()) - }) - await page.waitForLoadState('networkidle') + const { page, consoleLogs } = await renderPage(start) const slug = nav.replace(/\?.*$/, '').replace(/[/-]+/g, '-') await page.click(`[href^="${nav}"]`) @@ -1098,7 +1046,7 @@ describe('nested suspense', () => { const first = start.match(/\/suspense\/(?a?sync)-(?\d)\/(?a?sync)-(?\d)\//)!.groups! const last = nav.match(/\/suspense\/(?a?sync)-(?\d)\/(?a?sync)-(?\d)\//)!.groups! - expect(logs.sort()).toEqual([ + expect(consoleLogs.map(l => l.text).filter(i => !i.includes('[vite]') && !i.includes(' is an experimental feature')).sort()).toEqual([ // [first load] from parent `[${first.parentType}]`, ...first.parentType === 'async' ? ['[async] running async data'] : [], @@ -1122,14 +1070,7 @@ describe('nested suspense', () => { ] it.each(outwardNavigations)('should navigate from %s to a parent %s with no white flash', async (start, nav) => { - const page = await createPage(start, {}) - const logs: string[] = [] - page.on('console', (msg) => { - const text = msg.text() - if (text.includes('[vite]') || text.includes(' is an experimental feature')) { return } - logs.push(msg.text()) - }) - await page.waitForLoadState('networkidle') + const { page, consoleLogs } = await renderPage(start) await page.waitForSelector(`main:has(#child${start.replace(/[/-]+/g, '-')})`) @@ -1146,7 +1087,7 @@ describe('nested suspense', () => { const first = start.match(/\/suspense\/(?a?sync)-(?\d)\/(?a?sync)-(?\d)\//)!.groups! const last = nav.match(/\/suspense\/(?a?sync)-(?\d)\//)!.groups! - expect(logs.sort()).toEqual([ + expect(consoleLogs.map(l => l.text).filter(i => !i.includes('[vite]') && !i.includes(' is an experimental feature')).sort()).toEqual([ // [first load] from parent `[${first.parentType}]`, ...first.parentType === 'async' ? ['[async] running async data'] : [], @@ -1167,14 +1108,7 @@ describe('nested suspense', () => { ] it.each(inwardNavigations)('should navigate from %s to a child %s with no white flash', async (start, nav) => { - const page = await createPage(start, {}) - const logs: string[] = [] - page.on('console', (msg) => { - const text = msg.text() - if (text.includes('[vite]') || text.includes(' is an experimental feature')) { return } - logs.push(msg.text()) - }) - await page.waitForLoadState('networkidle') + const { page, consoleLogs } = await renderPage(start) const slug = nav.replace(/[/-]+/g, '-') await page.click(`[href^="${nav}"]`) @@ -1190,7 +1124,7 @@ describe('nested suspense', () => { const first = start.match(/\/suspense\/(?a?sync)-(?\d)\//)!.groups! const last = nav.match(/\/suspense\/(?a?sync)-(?\d)\/(?a?sync)-(?\d)\//)!.groups! - expect(logs.sort()).toEqual([ + expect(consoleLogs.map(l => l.text).filter(i => !i.includes('[vite]') && !i.includes(' is an experimental feature')).sort()).toEqual([ // [first load] from parent `[${first.parentType}]`, ...first.parentType === 'async' ? ['[async] running async data'] : [], @@ -1208,80 +1142,64 @@ describe('nested suspense', () => { // Bug #6592 describe('page key', () => { - it('should not cause run of setup if navigation not change page key and layout', async () => { - async function behaviour (path: string) { - await withLogs(async (page, logs) => { - await page.goto(url(`${path}/0`)) - await page.waitForLoadState('networkidle') + it.each(['/fixed-keyed-child-parent', '/internal-layout/fixed-keyed-child-parent'])('should not cause run of setup if navigation not change page key and layout', async (path) => { + const { page, consoleLogs } = await renderPage(`${path}/0`) - await page.click(`[href="${path}/1"]`) - await page.waitForSelector('#page-1') + await page.click(`[href="${path}/1"]`) + await page.waitForSelector('#page-1') - // Wait for all pending micro ticks to be cleared, - // so we are not resolved too early when there are repeated page loading - await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) + // Wait for all pending micro ticks to be cleared, + // so we are not resolved too early when there are repeated page loading + await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) - expect(logs.filter(l => l.includes('Child Setup')).length).toBe(1) - }) - } - await behaviour('/fixed-keyed-child-parent') - await behaviour('/internal-layout/fixed-keyed-child-parent') + expect(consoleLogs.filter(l => l.text.includes('Child Setup')).length).toBe(1) + await page.close() }) - it('will cause run of setup if navigation changed page key', async () => { - async function behaviour (path: string) { - await withLogs(async (page, logs) => { - await page.goto(url(`${path}/0`)) - await page.waitForLoadState('networkidle') - await page.click(`[href="${path}/1"]`) - await page.waitForSelector('#page-1') + it.each(['/keyed-child-parent', '/internal-layout/keyed-child-parent'])('will cause run of setup if navigation changed page key', async (path) => { + const { page, consoleLogs } = await renderPage(`${path}/0`) - // Wait for all pending micro ticks to be cleared, - // so we are not resolved too early when there are repeated page loading - await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) + await page.click(`[href="${path}/1"]`) + await page.waitForSelector('#page-1') - expect(logs.filter(l => l.includes('Child Setup')).length).toBe(2) - }) - } - await behaviour('/keyed-child-parent') - await behaviour('/internal-layout/keyed-child-parent') + // Wait for all pending micro ticks to be cleared, + // so we are not resolved too early when there are repeated page loading + await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) + + expect(consoleLogs.filter(l => l.text.includes('Child Setup')).length).toBe(2) + await page.close() }) }) // Bug #6592 describe('layout change not load page twice', () => { - async function behaviour (path1: string, path2: string) { - await withLogs(async (page, logs) => { - await page.goto(url(path1)) - await page.waitForLoadState('networkidle') - await page.click(`[href="${path2}"]`) - await page.waitForSelector('#with-layout2') - - // Wait for all pending micro ticks to be cleared, - // so we are not resolved too early when there are repeated page loading - await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) - - expect(logs.filter(l => l.includes('Layout2 Page Setup')).length).toBe(1) - }) + const cases = { + '/with-layout': '/with-layout2', + '/internal-layout/with-layout': '/internal-layout/with-layout2' } - it('should not cause run of page setup to repeat if layout changed', async () => { - await behaviour('/with-layout', '/with-layout2') - await behaviour('/internal-layout/with-layout', '/internal-layout/with-layout2') + + it.each(Object.entries(cases))('should not cause run of page setup to repeat if layout changed', async (path1, path2) => { + const { page, consoleLogs } = await renderPage(path1) + await page.click(`[href="${path2}"]`) + await page.waitForSelector('#with-layout2') + + // Wait for all pending micro ticks to be cleared, + // so we are not resolved too early when there are repeated page loading + await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) + + expect(consoleLogs.filter(l => l.text.includes('Layout2 Page Setup')).length).toBe(1) }) }) describe('layout switching', () => { // #13309 it('does not cause TypeError: Cannot read properties of null', async () => { - await withLogs(async (page, logs) => { - await page.goto(url('/layout-switch/start')) - await page.waitForLoadState('networkidle') - await page.click('[href="/layout-switch/end"]') - // Wait for all pending micro ticks to be cleared, - // so we are not resolved too early when there are repeated page loading - await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 10))) - expect(logs.filter(l => l.match(/error/i))).toMatchInlineSnapshot('[]') - }) + const { page, consoleLogs, pageErrors } = await renderPage('/layout-switch/start') + await page.click('[href="/layout-switch/end"]') + await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/layout-switch/end') + expect(consoleLogs.map(i => i.text).filter(l => l.match(/error/i))).toMatchInlineSnapshot('[]') + expect(pageErrors).toMatchInlineSnapshot('[]') + await page.close() }) }) @@ -1372,8 +1290,7 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => { }) it('still downloads client-only styles', async () => { - const page = await createPage('/styles') - await page.waitForLoadState('networkidle') + const { page } = await renderPage('/styles') expect(await page.$eval('.client-only-css', e => getComputedStyle(e).color)).toBe('rgb(50, 50, 50)') await page.close() @@ -1387,13 +1304,12 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => { describe('server components/islands', () => { it('/islands', async () => { - const page = await createPage('/islands') - await page.waitForLoadState('networkidle') + const { page } = await renderPage('/islands') await page.locator('#increase-pure-component').click() await page.waitForResponse(response => response.url().includes('/__nuxt_island/') && response.status() === 200) - await page.waitForLoadState('networkidle') - expect(await page.locator('#slot-in-server').first().innerHTML()).toContain('Slot with in .server component') - expect(await page.locator('#test-slot').first().innerHTML()).toContain('Slot with name test') + + await page.locator('#slot-in-server').getByText('Slot with in .server component').waitFor() + await page.locator('#test-slot').getByText('Slot with name test').waitFor() // test fallback slot with v-for expect(await page.locator('.fallback-slot-content').all()).toHaveLength(2) @@ -1404,9 +1320,9 @@ describe('server components/islands', () => { page.waitForResponse(response => response.url().includes('/__nuxt_island/LongAsyncComponent') && response.status() === 200), page.waitForResponse(response => response.url().includes('/__nuxt_island/AsyncServerComponent') && response.status() === 200) ]) - await page.waitForLoadState('networkidle') - expect(await page.locator('#async-server-component-count').innerHTML()).toContain(('1')) - expect(await page.locator('#long-async-component-count').innerHTML()).toContain('1') + + await page.locator('#async-server-component-count').getByText('1').waitFor() + await page.locator('#long-async-component-count').getByText('1').waitFor() // test islands slots interactivity await page.locator('#first-sugar-counter button').click() @@ -1420,7 +1336,7 @@ describe('server components/islands', () => { }) it('lazy server components', async () => { - const page = await createPage('/server-components/lazy/start') + const { page } = await renderPage('/server-components/lazy/start') await page.waitForLoadState('networkidle') await page.getByText('Go to page with lazy server component').click() @@ -1439,7 +1355,7 @@ describe('server components/islands', () => { }) it('non-lazy server components', async () => { - const page = await createPage('/server-components/lazy/start') + const { page } = await renderPage('/server-components/lazy/start') await page.waitForLoadState('networkidle') await page.getByText('Go to page without lazy server component').click() @@ -1477,14 +1393,9 @@ describe.skipIf(isDev() || isWindows || !isRenderingJson)('prefetching', () => { }) it('should prefetch everything needed when NuxtLink is used', async () => { - const page = await createPage() - const requests: string[] = [] + const { page, requests } = await renderPage() - page.on('request', (req) => { - requests.push(req.url().replace(url('/'), '/').replace(/\.[^.]+\./g, '.')) - }) - - await page.goto(url('/prefetch')) + await gotoPath(page, '/prefetch') await page.waitForLoadState('networkidle') const snapshot = [...requests] @@ -1782,15 +1693,15 @@ describe('component islands', () => { }) it('test client-side navigation', async () => { - const page = await createPage('/') - await page.waitForLoadState('networkidle') + const { page } = await renderPage('/') await page.click('#islands') - await page.waitForLoadState('networkidle') + await page.waitForFunction(() => window.useNuxtApp?.()._route.fullPath === '/islands') + await page.locator('#increase-pure-component').click() await page.waitForResponse(response => response.url().includes('/__nuxt_island/') && response.status() === 200) - await page.waitForLoadState('networkidle') - expect(await page.locator('#slot-in-server').first().innerHTML()).toContain('Slot with in .server component') - expect(await page.locator('#test-slot').first().innerHTML()).toContain('Slot with name test') + + await page.locator('#slot-in-server').getByText('Slot with in .server component').waitFor() + await page.locator('#test-slot').getByText('Slot with name test').waitFor() // test islands update expect(await page.locator('.box').innerHTML()).toContain('"number": 101,') @@ -1799,9 +1710,8 @@ describe('component islands', () => { page.waitForResponse(response => response.url().includes('/__nuxt_island/LongAsyncComponent') && response.status() === 200), page.waitForResponse(response => response.url().includes('/__nuxt_island/AsyncServerComponent') && response.status() === 200) ]) - await page.waitForLoadState('networkidle') - expect(await page.locator('#async-server-component-count').innerHTML()).toContain(('1')) - expect(await page.locator('#long-async-component-count').innerHTML()).toContain('1') + + await page.locator('#long-async-component-count').getByText('1').waitFor() // test islands slots interactivity await page.locator('#first-sugar-counter button').click() @@ -1848,18 +1758,12 @@ describe.skipIf(isDev() || isWindows || !isRenderingJson)('payload rendering', ( }) it('does not fetch a prefetched payload', async () => { - const page = await createPage() - const requests = [] as string[] + const { page, requests } = await renderPage() - page.on('request', (req) => { - requests.push(req.url().replace(url('/'), '/')) - }) - - await page.goto(url('/random/a')) - await page.waitForLoadState('networkidle') + await gotoPath(page, '/random/a') // We are manually prefetching other payloads - expect(requests).toContain('/random/c/_payload.json') + await page.waitForRequest(url('/random/c/_payload.json')) // We are not triggering API requests in the payload expect(requests).not.toContain(expect.stringContaining('/api/random')) @@ -1935,10 +1839,7 @@ describe.skipIf(isWindows)('useAsyncData', () => { expect(html).not.toContain('false') const page = await createPage('/useAsyncData/status') - await page.waitForLoadState('networkidle') - - expect(await page.locator('#status5-values').textContent()).toContain('idle,pending,success') - + await page.locator('#status5-values').getByText('idle,pending,success').waitFor() await page.close() }) }) diff --git a/test/hmr.test.ts b/test/hmr.test.ts index 20481c0688..bf1d241021 100644 --- a/test/hmr.test.ts +++ b/test/hmr.test.ts @@ -17,7 +17,7 @@ if (process.env.TEST_ENV !== 'built' && !isWindows) { dev: true, server: true, browser: true, - setupTimeout: (isWindows ? 240 : 120) * 1000, + setupTimeout: (isWindows ? 360 : 120) * 1000, nuxtConfig: { builder: isWebpack ? 'webpack' : 'vite', buildDir: process.env.NITRO_BUILD_DIR, diff --git a/test/runtime-compiler.test.ts b/test/runtime-compiler.test.ts index 392b66e89e..9a91746eda 100644 --- a/test/runtime-compiler.test.ts +++ b/test/runtime-compiler.test.ts @@ -1,7 +1,7 @@ import { fileURLToPath } from 'node:url' -import { isWindows } from 'std-env' import { describe, expect, it } from 'vitest' import { $fetch, setup } from '@nuxt/test-utils' +import { isWindows } from 'std-env' import { expectNoClientErrors, renderPage } from './utils' const isWebpack = process.env.TEST_BUILDER === 'webpack' @@ -10,7 +10,7 @@ await setup({ dev: process.env.TEST_ENV === 'dev', server: true, browser: true, - setupTimeout: (isWindows ? 240 : 120) * 1000, + setupTimeout: (isWindows ? 360 : 120) * 1000, nuxtConfig: { builder: isWebpack ? 'webpack' : 'vite' } diff --git a/test/utils.ts b/test/utils.ts index bd1015b262..e12f10e2a3 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -4,7 +4,7 @@ import type { Page } from 'playwright-core' import { parse } from 'devalue' import { reactive, ref, shallowReactive, shallowRef } from 'vue' import { createError } from 'h3' -import { createPage, getBrowser, url, useTestContext } from '@nuxt/test-utils' +import { getBrowser, url, useTestContext } from '@nuxt/test-utils' export const isRenderingJson = true @@ -17,6 +17,7 @@ export async function renderPage (path = '/') { const browser = await getBrowser() const page = await browser.newPage({}) const pageErrors: Error[] = [] + const requests: string[] = [] const consoleLogs: { type: string, text: string }[] = [] page.on('console', (message) => { @@ -28,14 +29,19 @@ export async function renderPage (path = '/') { page.on('pageerror', (err) => { pageErrors.push(err) }) + page.on('request', (req) => { + requests.push(req.url().replace(url('/'), '/')) + }) if (path) { await page.goto(url(path), { waitUntil: 'networkidle' }) + await page.waitForFunction(() => window.useNuxtApp?.()) } return { page, pageErrors, + requests, consoleLogs } } @@ -58,6 +64,11 @@ export async function expectNoClientErrors (path: string) { await page.close() } +export async function gotoPath (page: Page, path: string) { + await page.goto(url(path)) + await page.waitForFunction(path => window.useNuxtApp?.()._route.fullPath === path, path) +} + type EqualityVal = string | number | boolean | null | undefined | RegExp export async function expectWithPolling ( get: () => Promise | EqualityVal, @@ -76,26 +87,6 @@ export async function expectWithPolling ( expect(result?.toString(), `"${result?.toString()}" did not equal "${expected?.toString()}" in ${retries * delay}ms`).toEqual(expected?.toString()) } -export async function withLogs (callback: (page: Page, logs: string[]) => Promise) { - let done = false - const page = await createPage() - const logs: string[] = [] - page.on('console', (msg) => { - const text = msg.text() - if (done && !text.includes('[vite] server connection lost')) { - throw new Error(`Test finished prematurely before log: [${msg.type()}] ${text}`) - } - logs.push(text) - }) - - try { - await callback(page, logs) - } finally { - done = true - await page.close() - } -} - const revivers = { NuxtError: (data: any) => createError(data), EmptyShallowRef: (data: any) => shallowRef(JSON.parse(data)),