mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
The hypothesis is that this is just installing some font stuff that isn't actually needed for Chromium to work: ``` The following additional packages will be installed: libnss3-tools xfonts-encodings xfonts-utils Recommended packages: fonts-ipafont-mincho fonts-liberation-sans-narrow fonts-tlwg-loma The following NEW packages will be installed: fonts-freefont-ttf fonts-ipafont-gothic fonts-liberation fonts-tlwg-loma-otf fonts-unifont fonts-wqy-zenhei xfonts-cyrillic xfonts-encodings xfonts-scalable xfonts-utils The following packages will be upgraded: libnss3 libnss3-tools 2 upgraded, 10 newly installed, 0 to remove and 35 not upgraded. ``` `apt` is very slow, so skipping this step should significantly reduce setup time per test shard. It seems like this isn't always true for other browsers, so only skip this for Chromium (that's fine, that's what most of our jobs use anyways). On 16 core arm runners, "Install Playwright Browsers" typically took 18-24 seconds. Now it takes 5-6s. Let's estimate that saves about 15s per Linux job that installs Chromium. Claude estimates that there are 73 chromium-only Linux jobs on this PR: <img width="891" height="148" alt="Screenshot 2026-07-06 at 10 30 16 PM" src="https://github.com/user-attachments/assets/725893fa-9f6f-4806-a3b4-9858ab0ebe66" /> So that's 15*73/60=18.25 minutes saved per PR push (the average PR gets many pushes). At the public (before any volume discounts) price of $0.026/minute for the 16 core arm runners, that comes out to $0.4745 per PR push saved. This workflow has had 3,894 runs in the last 30 days, so that could be $1,847/mo saved, but many of those workflow runs were canceled, and some likely failed before getting to these jobs, so the actual savings are probably somewhere around half of that -- scroll through https://github.com/vercel/next.js/actions/workflows/.github/workflows/build_and_test.yml and see that a little less than half of the workflow runs end up canceled.
261 lines
7.9 KiB
TypeScript
261 lines
7.9 KiB
TypeScript
/* eslint-env jest */
|
|
|
|
import path from 'path'
|
|
import { nextTestSetup } from 'e2e-utils'
|
|
import { retry } from 'next-test-utils'
|
|
import { expectScrolledTo } from './scroll-position.util'
|
|
|
|
describe('Client navigation with URL hash', () => {
|
|
const { next } = nextTestSetup({
|
|
files: path.join(__dirname, 'fixture'),
|
|
env: {
|
|
TEST_STRICT_NEXT_HEAD: String(true),
|
|
},
|
|
})
|
|
|
|
describe('when hash changes', () => {
|
|
describe('check hydration mis-match', () => {
|
|
it('should not have hydration mis-match for hash link', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
const browserLogs = await browser.log()
|
|
let found = false
|
|
browserLogs.forEach((log) => {
|
|
console.log('log.message', log.message)
|
|
if (log.message.includes('Warning: Prop')) {
|
|
found = true
|
|
}
|
|
})
|
|
expect(found).toEqual(false)
|
|
})
|
|
})
|
|
|
|
describe('when hash change via Link', () => {
|
|
it('should not run getInitialProps', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
|
|
await browser.elementByCss('#via-link').click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('p').text()).toBe('COUNT: 0')
|
|
})
|
|
|
|
await browser.close()
|
|
})
|
|
|
|
it('should scroll to the specified position on the same page', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
|
|
// Scrolls to item 400 on the page
|
|
await browser.elementByCss('#scroll-to-item-400').click()
|
|
|
|
await expectScrolledTo(browser, "document.getElementById('item-400')")
|
|
|
|
// Scrolls back to top when scrolling to `#` with no value.
|
|
await browser.elementByCss('#via-empty-hash').click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.eval('window.pageYOffset')).toBe(0)
|
|
})
|
|
|
|
// Scrolls to item 400 on the page
|
|
await browser.elementByCss('#scroll-to-item-400').click()
|
|
|
|
await expectScrolledTo(browser, "document.getElementById('item-400')")
|
|
|
|
// Scrolls back to top when clicking link with href `#top`.
|
|
await browser.elementByCss('#via-top-hash').click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.eval('window.pageYOffset')).toBe(0)
|
|
})
|
|
|
|
// Scrolls to cjk anchor on the page
|
|
await browser.elementByCss('#scroll-to-cjk-anchor').click()
|
|
|
|
await expectScrolledTo(browser, "document.getElementById('中文')")
|
|
})
|
|
|
|
it('should not scroll to hash when scroll={false} is set', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
const curScroll = await browser.eval(
|
|
'document.documentElement.scrollTop'
|
|
)
|
|
await browser.elementByCss('#scroll-to-name-item-400-no-scroll').click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.eval('document.documentElement.scrollTop')).toBe(
|
|
curScroll
|
|
)
|
|
})
|
|
})
|
|
|
|
it('should scroll to the specified position on the same page with a name property', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
|
|
// Scrolls to item 400 with name="name-item-400" on the page
|
|
await browser.elementByCss('#scroll-to-name-item-400').click()
|
|
|
|
await expectScrolledTo(
|
|
browser,
|
|
"document.getElementsByName('name-item-400')[0]"
|
|
)
|
|
|
|
// Scrolls back to top when scrolling to `#` with no value.
|
|
await browser.elementByCss('#via-empty-hash').click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.eval('window.pageYOffset')).toBe(0)
|
|
})
|
|
})
|
|
|
|
it('should scroll to the specified position to a new page', async () => {
|
|
const browser = await next.browser('/nav')
|
|
|
|
// Scrolls to item 400 on the page
|
|
await browser
|
|
.elementByCss('#scroll-to-hash')
|
|
.click()
|
|
.waitForElementByCss('#hash-changes-page')
|
|
|
|
await expectScrolledTo(browser, "document.getElementById('item-400')")
|
|
})
|
|
|
|
it('should scroll to the specified CJK position to a new page', async () => {
|
|
const browser = await next.browser('/nav')
|
|
|
|
// Scrolls to CJK anchor on the page
|
|
await browser
|
|
.elementByCss('#scroll-to-cjk-hash')
|
|
.click()
|
|
.waitForElementByCss('#hash-changes-page')
|
|
|
|
await expectScrolledTo(browser, "document.getElementById('中文')")
|
|
})
|
|
|
|
it('Should update asPath', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
|
|
await browser.elementByCss('#via-link').click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('div#asPath').text()).toBe(
|
|
'ASPATH: /nav/hash-changes#via-link'
|
|
)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('when hash change via A tag', () => {
|
|
it('should not run getInitialProps', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
|
|
await browser.elementByCss('#via-a').click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('p').text()).toBe('COUNT: 0')
|
|
})
|
|
|
|
await browser.close()
|
|
})
|
|
})
|
|
|
|
describe('when hash get removed', () => {
|
|
it('should not run getInitialProps', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
|
|
await browser
|
|
.elementByCss('#via-a')
|
|
.click()
|
|
.elementByCss('#page-url')
|
|
.click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('p').text()).toBe('COUNT: 1')
|
|
})
|
|
|
|
await browser.close()
|
|
})
|
|
|
|
it('should not run getInitialProps when removing via back', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
|
|
await browser.elementByCss('#scroll-to-item-400').click()
|
|
await browser.back()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('p').text()).toBe('COUNT: 0')
|
|
})
|
|
|
|
await browser.close()
|
|
})
|
|
})
|
|
|
|
describe('when hash set to empty', () => {
|
|
it('should not run getInitialProps', async () => {
|
|
const browser = await next.browser('/nav/hash-changes')
|
|
|
|
await browser
|
|
.elementByCss('#via-a')
|
|
.click()
|
|
.elementByCss('#via-empty-hash')
|
|
.click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('p').text()).toBe('COUNT: 0')
|
|
})
|
|
|
|
await browser.close()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('when hash changes with state', () => {
|
|
describe('when passing state via hash change', () => {
|
|
it('should increment the history state counter', async () => {
|
|
const browser = await next.browser('/nav/hash-changes-with-state#')
|
|
|
|
await browser
|
|
.elementByCss('#increment-history-count')
|
|
.click()
|
|
.elementByCss('#increment-history-count')
|
|
.click()
|
|
|
|
await retry(async () => {
|
|
expect(await browser.elementByCss('div#history-count').text()).toBe(
|
|
'HISTORY COUNT: 2'
|
|
)
|
|
})
|
|
|
|
const counter = await browser.elementByCss('p').text()
|
|
|
|
// getInitialProps should not be called with only hash changes
|
|
expect(counter).toBe('COUNT: 0')
|
|
|
|
await browser.close()
|
|
})
|
|
|
|
it('should increment the shallow history state counter', async () => {
|
|
const browser = await next.browser('/nav/hash-changes-with-state#')
|
|
|
|
await browser
|
|
.elementByCss('#increment-shallow-history-count')
|
|
.click()
|
|
.elementByCss('#increment-shallow-history-count')
|
|
.click()
|
|
|
|
await retry(async () => {
|
|
expect(
|
|
await browser.elementByCss('div#shallow-history-count').text()
|
|
).toBe('SHALLOW HISTORY COUNT: 2')
|
|
})
|
|
|
|
const counter = await browser.elementByCss('p').text()
|
|
|
|
expect(counter).toBe('COUNT: 0')
|
|
|
|
await browser.close()
|
|
})
|
|
})
|
|
})
|
|
})
|