refactor(tests): make chain more "correct" (#51728)
### Why? I really dislike the way `.chain` works right now, it shouldn't mutate the `BrowserInterface`, this PR changes it so it's just a pure chain without weird side effects. One example with the current version (before this PR): ``` const el = browser.elementByCss('#version-2') await el.text() // throws await el.text() ``` ### Additional Changes - removes selenium (which is completely unused) - updates playwright - makes the playwright tracing not error all the time
This commit is contained in:
parent
f336bd6ada
commit
60f0837b67
33 changed files with 207 additions and 689 deletions
|
@ -185,8 +185,8 @@
|
|||
"open": "9.0.0",
|
||||
"outdent": "0.8.0",
|
||||
"pixrem": "5.0.0",
|
||||
"playwright-chromium": "1.35.1",
|
||||
"playwright-core": "1.35.1",
|
||||
"playwright": "1.41.2",
|
||||
"playwright-chromium": "1.41.2",
|
||||
"postcss": "8.4.31",
|
||||
"postcss-nested": "4.2.1",
|
||||
"postcss-pseudoelements": "5.0.0",
|
||||
|
|
|
@ -150,7 +150,7 @@
|
|||
"@next/react-refresh-utils": "14.1.1-canary.52",
|
||||
"@next/swc": "14.1.1-canary.52",
|
||||
"@opentelemetry/api": "1.6.0",
|
||||
"@playwright/test": "^1.35.1",
|
||||
"@playwright/test": "1.41.2",
|
||||
"@taskr/clear": "1.1.0",
|
||||
"@taskr/esnext": "1.1.0",
|
||||
"@types/amphtml-validator": "1.0.0",
|
||||
|
@ -309,7 +309,7 @@
|
|||
"vm-browserify": "1.1.2",
|
||||
"watchpack": "2.4.0",
|
||||
"web-vitals": "3.0.0",
|
||||
"webpack": "5.86.0",
|
||||
"webpack": "5.90.0",
|
||||
"webpack-sources1": "npm:webpack-sources@1.4.3",
|
||||
"webpack-sources3": "npm:webpack-sources@3.2.3",
|
||||
"ws": "8.2.3",
|
||||
|
|
|
@ -401,12 +401,12 @@ importers:
|
|||
pixrem:
|
||||
specifier: 5.0.0
|
||||
version: 5.0.0
|
||||
playwright:
|
||||
specifier: 1.41.2
|
||||
version: 1.41.2
|
||||
playwright-chromium:
|
||||
specifier: 1.35.1
|
||||
version: 1.35.1
|
||||
playwright-core:
|
||||
specifier: 1.35.1
|
||||
version: 1.35.1
|
||||
specifier: 1.41.2
|
||||
version: 1.41.2
|
||||
postcss:
|
||||
specifier: 8.4.31
|
||||
version: 8.4.31
|
||||
|
@ -948,8 +948,8 @@ importers:
|
|||
specifier: 1.6.0
|
||||
version: 1.6.0
|
||||
'@playwright/test':
|
||||
specifier: ^1.35.1
|
||||
version: 1.35.1
|
||||
specifier: 1.41.2
|
||||
version: 1.41.2
|
||||
'@taskr/clear':
|
||||
specifier: 1.1.0
|
||||
version: 1.1.0
|
||||
|
@ -5950,15 +5950,12 @@ packages:
|
|||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/@playwright/test@1.35.1:
|
||||
resolution: {integrity: sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA==}
|
||||
/@playwright/test@1.41.2:
|
||||
resolution: {integrity: sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/node': 20.2.5
|
||||
playwright-core: 1.35.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
playwright: 1.41.2
|
||||
dev: true
|
||||
|
||||
/@polka/url@1.0.0-next.11:
|
||||
|
@ -19421,13 +19418,13 @@ packages:
|
|||
resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
|
||||
dev: true
|
||||
|
||||
/playwright-chromium@1.35.1:
|
||||
resolution: {integrity: sha512-bPuWIk/DYWtrg10ajcc9cZLo5s9XQrs6JF6wwiieqZ73rCG3PLVEm0RX8fCUzziuEAhYsoL09UNuKSKv7pKz9A==}
|
||||
/playwright-chromium@1.41.2:
|
||||
resolution: {integrity: sha512-1XoW4aGGRbS2BJLldtLcv2QW3deMv8myE5iCtfGRPq99BWqmBLJvJTgY/SyfBCoklwQvl91zUWYWHjCAuvKGkw==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
playwright-core: 1.35.1
|
||||
playwright-core: 1.41.2
|
||||
dev: true
|
||||
|
||||
/playwright-core@1.19.2:
|
||||
|
@ -19457,12 +19454,22 @@ packages:
|
|||
- utf-8-validate
|
||||
dev: true
|
||||
|
||||
/playwright-core@1.35.1:
|
||||
resolution: {integrity: sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==}
|
||||
/playwright-core@1.41.2:
|
||||
resolution: {integrity: sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/playwright@1.41.2:
|
||||
resolution: {integrity: sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==}
|
||||
engines: {node: '>=16'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
playwright-core: 1.41.2
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/please-upgrade-node@3.2.0:
|
||||
resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==}
|
||||
dependencies:
|
||||
|
|
|
@ -1783,17 +1783,17 @@ createNextDescribe(
|
|||
|
||||
await browser.elementByCss('a').click()
|
||||
|
||||
browser.waitForElementByCss('#relative-1')
|
||||
await browser.waitForElementByCss('#relative-1')
|
||||
page = await browser.elementByCss('body').text()
|
||||
expect(page).toMatch(/On relative 1/)
|
||||
await browser.elementByCss('a').click()
|
||||
|
||||
browser.waitForElementByCss('#relative-2')
|
||||
await browser.waitForElementByCss('#relative-2')
|
||||
page = await browser.elementByCss('body').text()
|
||||
expect(page).toMatch(/On relative 2/)
|
||||
|
||||
await browser.elementByCss('button').click()
|
||||
browser.waitForElementByCss('#relative')
|
||||
await browser.waitForElementByCss('#relative')
|
||||
page = await browser.elementByCss('body').text()
|
||||
expect(page).toMatch(/On relative index/)
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ createNextDescribe(
|
|||
pagePath,
|
||||
pageContent.replaceAll('version-1', 'version-2')
|
||||
)
|
||||
browser.waitForElementByCss('#version-2')
|
||||
await browser.waitForElementByCss('#version-2')
|
||||
expect(await browser.elementByCss('p').text()).toBe('version-2')
|
||||
|
||||
// Verify no hydration mismatch:
|
||||
|
@ -33,13 +33,13 @@ createNextDescribe(
|
|||
pagePath,
|
||||
pageContent.replaceAll('version-1', 'version-3')
|
||||
)
|
||||
browser.waitForElementByCss('#version-3')
|
||||
await browser.waitForElementByCss('#version-3')
|
||||
expect(await browser.elementByCss('p').text()).toBe('version-3')
|
||||
|
||||
// Verify no hydration mismatch:
|
||||
expect(await hasRedbox(browser)).toBeFalse()
|
||||
|
||||
browser.refresh()
|
||||
await browser.refresh()
|
||||
|
||||
// Verify no hydration mismatch:
|
||||
expect(await hasRedbox(browser)).toBeFalse()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable jest/no-standalone-expect */
|
||||
import { createNextDescribe } from 'e2e-utils'
|
||||
import { check } from 'next-test-utils'
|
||||
import type { Response } from 'playwright-chromium'
|
||||
import type { Response } from 'playwright'
|
||||
|
||||
createNextDescribe(
|
||||
'app-dir action progressive enhancement',
|
||||
|
@ -14,7 +14,7 @@ createNextDescribe(
|
|||
'server-only': 'latest',
|
||||
},
|
||||
},
|
||||
({ next, isNextDev, isNextStart, isNextDeploy }) => {
|
||||
({ next }) => {
|
||||
it('should support formData and redirect without JS', async () => {
|
||||
let responseCode
|
||||
const browser = await next.browser('/server', {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable jest/no-standalone-expect */
|
||||
import { createNextDescribe } from 'e2e-utils'
|
||||
import { check, waitFor } from 'next-test-utils'
|
||||
import { Request, Response, Route } from 'playwright-chromium'
|
||||
import type { Request, Response, Route } from 'playwright'
|
||||
import fs from 'fs-extra'
|
||||
import { join } from 'path'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { createNextDescribe } from 'e2e-utils'
|
||||
import { check } from 'next-test-utils'
|
||||
import { BrowserInterface } from 'test/lib/browsers/base'
|
||||
import { Request } from 'playwright-chromium'
|
||||
import type { Request } from 'playwright'
|
||||
|
||||
const getPathname = (url: string) => {
|
||||
const urlObj = new URL(url)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { Request } from 'playwright-core'
|
||||
import type { Request } from 'playwright'
|
||||
|
||||
import { createNextDescribe } from 'e2e-utils'
|
||||
import type { BrowserInterface } from '../../../lib/browsers/base'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { nextTestSetup, FileRef } from 'e2e-utils'
|
||||
import { check } from 'next-test-utils'
|
||||
import { join } from 'path'
|
||||
import { Response } from 'playwright-chromium'
|
||||
import { Response } from 'playwright'
|
||||
|
||||
describe('interception-route-prefetch-cache', () => {
|
||||
function runTests({ next }: ReturnType<typeof nextTestSetup>) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createNextDescribe } from 'e2e-utils'
|
||||
import { retry, waitFor } from 'next-test-utils'
|
||||
import type { Request } from 'playwright-chromium'
|
||||
import type { Request } from 'playwright'
|
||||
|
||||
createNextDescribe(
|
||||
'app dir - navigation',
|
||||
|
@ -35,7 +35,7 @@ createNextDescribe(
|
|||
}> = []
|
||||
|
||||
const browser = await next.browser('/search-params?name=名')
|
||||
browser.on('request', async (req: Request) => {
|
||||
async function requestHandler(req: Request) {
|
||||
const res = await req.response()
|
||||
if (!res) return
|
||||
|
||||
|
@ -44,7 +44,8 @@ createNextDescribe(
|
|||
ok: res.ok(),
|
||||
headers: res.headers(),
|
||||
})
|
||||
})
|
||||
}
|
||||
browser.on('request', requestHandler)
|
||||
expect(await browser.elementById('name').text()).toBe('名')
|
||||
await browser.elementById('link').click()
|
||||
|
||||
|
@ -57,6 +58,9 @@ createNextDescribe(
|
|||
}),
|
||||
})
|
||||
)
|
||||
|
||||
browser.off('request', requestHandler)
|
||||
await browser.close()
|
||||
})
|
||||
|
||||
it('should not reset shallow url updates on prefetch', async () => {
|
||||
|
|
|
@ -133,7 +133,7 @@ createNextDescribe(
|
|||
})
|
||||
`)
|
||||
await waitForScrollToComplete(browser, { x: 0, y: 10000 })
|
||||
browser.quit()
|
||||
browser.close()
|
||||
})
|
||||
|
||||
// Test hot reloading only in development
|
||||
|
|
|
@ -30,7 +30,6 @@ describe('i18n: Event with stale state - static route previously was dynamic', (
|
|||
|
||||
test('Ignore event without query param', async () => {
|
||||
const browser = await webdriver(next.url, '/sv/static')
|
||||
browser.close()
|
||||
|
||||
const state: HistoryState = {
|
||||
url: '/[dynamic]?',
|
||||
|
|
|
@ -30,7 +30,6 @@ describe('Event with stale state - static route previously was dynamic', () => {
|
|||
|
||||
test('Ignore event without query param', async () => {
|
||||
const browser = await webdriver(next.url, '/static')
|
||||
browser.close()
|
||||
|
||||
const state: HistoryState = {
|
||||
url: '/[dynamic]?',
|
||||
|
|
|
@ -720,14 +720,14 @@ describe('Middleware Runtime', () => {
|
|||
requests.push(x.url())
|
||||
})
|
||||
|
||||
browser.elementById('deep-link').click()
|
||||
browser.waitForElementByCss('[data-query-hello="goodbye"]')
|
||||
await browser.elementById('deep-link').click()
|
||||
await browser.waitForElementByCss('[data-query-hello="goodbye"]')
|
||||
const deepLinkMessage = await getMessageContents()
|
||||
expect(deepLinkMessage).not.toEqual(ssrMessage)
|
||||
|
||||
// Changing the route with a shallow link should not cause a server request
|
||||
browser.elementById('shallow-link').click()
|
||||
browser.waitForElementByCss('[data-query-hello="world"]')
|
||||
await browser.elementById('shallow-link').click()
|
||||
await browser.waitForElementByCss('[data-query-hello="world"]')
|
||||
expect(await getMessageContents()).toEqual(deepLinkMessage)
|
||||
|
||||
// Check that no server requests were made to ?hello=world,
|
||||
|
|
|
@ -816,9 +816,9 @@ describe('Middleware Rewrite', () => {
|
|||
const element = await browser.elementByCss('.title')
|
||||
expect(await element.text()).toEqual('Parts page')
|
||||
const logs = await browser.log()
|
||||
expect(
|
||||
logs.every((log) => log.source === 'log' || log.source === 'info')
|
||||
).toEqual(true)
|
||||
expect(logs).toSatisfyAll(
|
||||
(log) => log.source === 'log' || log.source === 'info'
|
||||
)
|
||||
})
|
||||
|
||||
it('should not have unexpected errors', async () => {
|
||||
|
|
|
@ -470,14 +470,14 @@ describe('Middleware Runtime trailing slash', () => {
|
|||
requests.push(x.url())
|
||||
})
|
||||
|
||||
browser.elementById('deep-link').click()
|
||||
browser.waitForElementByCss('[data-query-hello="goodbye"]')
|
||||
await browser.elementById('deep-link').click()
|
||||
await browser.waitForElementByCss('[data-query-hello="goodbye"]')
|
||||
const deepLinkMessage = await getMessageContents()
|
||||
expect(deepLinkMessage).not.toEqual(ssrMessage)
|
||||
|
||||
// Changing the route with a shallow link should not cause a server request
|
||||
browser.elementById('shallow-link').click()
|
||||
browser.waitForElementByCss('[data-query-hello="world"]')
|
||||
await browser.elementById('shallow-link').click()
|
||||
await browser.waitForElementByCss('[data-query-hello="world"]')
|
||||
expect(await getMessageContents()).toEqual(deepLinkMessage)
|
||||
|
||||
// Check that no server requests were made to ?hello=world,
|
||||
|
|
|
@ -35,7 +35,7 @@ describe('New Link Behavior with material-ui', () => {
|
|||
|
||||
it('should render MuiLink with <a>', async () => {
|
||||
const browser = await webdriver(next.url, `/`)
|
||||
const element = await browser.elementByCss('a[href="/about"]')
|
||||
const element = browser.elementByCss('a[href="/about"]')
|
||||
|
||||
const color = await element.getComputedCss('color')
|
||||
expect(color).toBe('rgb(25, 133, 123)')
|
||||
|
|
|
@ -160,8 +160,7 @@ describe('Font Optimization', () => {
|
|||
)
|
||||
expect(baseFont).toBeDefined()
|
||||
|
||||
await browser.waitForElementByCss('#with-font')
|
||||
await browser.click('#with-font')
|
||||
await browser.waitForElementByCss('#with-font').click()
|
||||
|
||||
await browser.waitForElementByCss('#with-font-container')
|
||||
const pageFontCss = await browser.elementsByCss(
|
||||
|
|
|
@ -61,10 +61,8 @@ function getRatio(width, height) {
|
|||
|
||||
function runTests(mode) {
|
||||
it('should load the images', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs')
|
||||
|
||||
await check(async () => {
|
||||
const result = await browser.eval(
|
||||
`document.getElementById('basic-image').naturalWidth`
|
||||
|
@ -84,17 +82,13 @@ function runTests(mode) {
|
|||
)
|
||||
).toBe(true)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('should update the image on src change', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/update')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/update')
|
||||
|
||||
await check(
|
||||
() => browser.eval(`document.getElementById("update-image").src`),
|
||||
/test\.jpg/
|
||||
|
@ -107,16 +101,13 @@ function runTests(mode) {
|
|||
/test\.png/
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('should work when using flexbox', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/flex')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/flex')
|
||||
await check(async () => {
|
||||
const result = await browser.eval(
|
||||
`document.getElementById('basic-image').width`
|
||||
|
@ -128,16 +119,13 @@ function runTests(mode) {
|
|||
return 'result-correct'
|
||||
}, /result-correct/)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('should work with layout-fixed so resizing window does not resize image', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/layout-fixed')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/layout-fixed')
|
||||
const width = 1200
|
||||
const height = 700
|
||||
const delta = 250
|
||||
|
@ -162,16 +150,13 @@ function runTests(mode) {
|
|||
expect(await getComputed(browser, id, 'width')).toBe(width)
|
||||
expect(await getComputed(browser, id, 'height')).toBe(height)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('should work with layout-intrinsic so resizing window maintains image aspect ratio', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/layout-intrinsic')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/layout-intrinsic')
|
||||
const width = 1200
|
||||
const height = 700
|
||||
const delta = 250
|
||||
|
@ -206,16 +191,13 @@ function runTests(mode) {
|
|||
1
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('should work with layout-responsive so resizing window maintains image aspect ratio', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/layout-responsive')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/layout-responsive')
|
||||
const width = 1200
|
||||
const height = 700
|
||||
const delta = 250
|
||||
|
@ -250,16 +232,13 @@ function runTests(mode) {
|
|||
1
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('should work with layout-fill to fill the parent but NOT stretch with viewport', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/layout-fill')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/layout-fill')
|
||||
const width = 600
|
||||
const height = 350
|
||||
const delta = 150
|
||||
|
@ -294,16 +273,13 @@ function runTests(mode) {
|
|||
1
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('should work with layout-fill to fill the parent and stretch with viewport', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/layout-fill')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/layout-fill')
|
||||
const id = 'fill2'
|
||||
const width = await getComputed(browser, id, 'width')
|
||||
const height = await getComputed(browser, id, 'height')
|
||||
|
@ -348,16 +324,13 @@ function runTests(mode) {
|
|||
expect(objectFit).toBe('cover')
|
||||
expect(objectPosition).toBe('left center')
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
it('should work with sizes and automatically use layout-responsive', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/sizes')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/sizes')
|
||||
const width = 1200
|
||||
const height = 700
|
||||
const delta = 250
|
||||
|
@ -394,9 +367,7 @@ function runTests(mode) {
|
|||
1
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -434,10 +405,8 @@ function runTests(mode) {
|
|||
}
|
||||
|
||||
it('should correctly ignore prose styles', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/prose')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/prose')
|
||||
|
||||
const id = 'prose-image'
|
||||
|
||||
// Wait for image to load:
|
||||
|
@ -459,19 +428,15 @@ function runTests(mode) {
|
|||
const computedHeight = await getComputed(browser, id, 'height')
|
||||
expect(getRatio(computedWidth, computedHeight)).toBeCloseTo(1, 1)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
// Tests that use the `unsized` attribute:
|
||||
if (mode !== 'dev') {
|
||||
it('should correctly rotate image', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/docs/rotated')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/docs/rotated')
|
||||
|
||||
const id = 'exif-rotation-image'
|
||||
|
||||
// Wait for image to load:
|
||||
|
@ -493,9 +458,7 @@ function runTests(mode) {
|
|||
const computedHeight = await getComputed(browser, id, 'height')
|
||||
expect(getRatio(computedWidth, computedHeight)).toBeCloseTo(0.5625, 1)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,25 +1,11 @@
|
|||
import React from 'react'
|
||||
import Image from 'next/legacy/image'
|
||||
import img from '../public/test.jpg'
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Priority Missing Warning Page</h1>
|
||||
<Image
|
||||
id="responsive"
|
||||
layout="responsive"
|
||||
src="/wide.png"
|
||||
width="1200"
|
||||
height="700"
|
||||
/>
|
||||
<Image
|
||||
id="fixed"
|
||||
layout="fixed"
|
||||
src="/test.jpg"
|
||||
width="400"
|
||||
height="400"
|
||||
/>
|
||||
<footer>Priority Missing Warning Footer</footer>
|
||||
<Image id="responsive" src={img} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -915,9 +915,8 @@ function runTests(mode) {
|
|||
})
|
||||
|
||||
it('should warn when priority prop is missing on LCP image', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/priority-missing-warning')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/priority-missing-warning')
|
||||
// Wait for image to load:
|
||||
await check(async () => {
|
||||
const result = await browser.eval(
|
||||
|
@ -934,12 +933,10 @@ function runTests(mode) {
|
|||
.join('\n')
|
||||
expect(await hasRedbox(browser)).toBe(false)
|
||||
expect(warnings).toMatch(
|
||||
/Image with src (.*)wide.png(.*) was detected as the Largest Contentful Paint/gm
|
||||
/Image with src (.*)test(.*) was detected as the Largest Contentful Paint/gm
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import img from '../../public/test.jpg'
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Priority Missing Warning Page</h1>
|
||||
<Image id="responsive" src="/wide.png" width="1200" height="700" />
|
||||
<Image id="fixed" src="/test.jpg" width="400" height="400" />
|
||||
<footer>Priority Missing Warning Footer</footer>
|
||||
<Image id="responsive" src={img} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -990,9 +990,8 @@ function runTests(mode) {
|
|||
})
|
||||
|
||||
it('should warn when priority prop is missing on LCP image', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/priority-missing-warning')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/priority-missing-warning')
|
||||
// Wait for image to load:
|
||||
await check(async () => {
|
||||
const result = await browser.eval(
|
||||
|
@ -1009,12 +1008,10 @@ function runTests(mode) {
|
|||
.join('\n')
|
||||
expect(await hasRedbox(browser)).toBe(false)
|
||||
expect(warnings).toMatch(
|
||||
/Image with src (.*)wide.png(.*) was detected as the Largest Contentful Paint/gm
|
||||
/Image with src (.*)test(.*) was detected as the Largest Contentful Paint/gm
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import img from '../public/test.jpg'
|
||||
|
||||
const Page = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Priority Missing Warning Page</h1>
|
||||
<Image id="responsive" src="/wide.png" width="1200" height="700" />
|
||||
<Image id="fixed" src="/test.jpg" width="400" height="400" />
|
||||
<footer>Priority Missing Warning Footer</footer>
|
||||
<Image id="responsive" src={img} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -991,9 +991,8 @@ function runTests(mode) {
|
|||
})
|
||||
|
||||
it('should warn when priority prop is missing on LCP image', async () => {
|
||||
let browser
|
||||
let browser = await webdriver(appPort, '/priority-missing-warning')
|
||||
try {
|
||||
browser = await webdriver(appPort, '/priority-missing-warning')
|
||||
// Wait for image to load:
|
||||
await check(async () => {
|
||||
const result = await browser.eval(
|
||||
|
@ -1005,17 +1004,15 @@ function runTests(mode) {
|
|||
return 'done'
|
||||
}, 'done')
|
||||
await waitFor(1000)
|
||||
const warnings = (await browser.log('browser'))
|
||||
const warnings = (await browser.log())
|
||||
.map((log) => log.message)
|
||||
.join('\n')
|
||||
expect(await hasRedbox(browser)).toBe(false)
|
||||
expect(warnings).toMatch(
|
||||
/Image with src (.*)wide.png(.*) was detected as the Largest Contentful Paint/gm
|
||||
/Image with src (.*)test(.*) was detected as the Largest Contentful Paint/gm
|
||||
)
|
||||
} finally {
|
||||
if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
await browser.close()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -154,8 +154,7 @@ const runTests = (isDev) => {
|
|||
)
|
||||
expect(documentBIScripts.length).toBe(2)
|
||||
|
||||
await browser.waitForElementByCss('[href="/page1"]')
|
||||
await browser.click('[href="/page1"]')
|
||||
await browser.waitForElementByCss('[href="/page1"]').click()
|
||||
|
||||
await browser.waitForElementByCss('.container')
|
||||
|
||||
|
@ -179,10 +178,8 @@ const runTests = (isDev) => {
|
|||
expect(text).toBe('aaabbbccc')
|
||||
|
||||
// Navigate to different page and back
|
||||
await browser.waitForElementByCss('[href="/page9"]')
|
||||
await browser.click('[href="/page9"]')
|
||||
await browser.waitForElementByCss('[href="/page4"]')
|
||||
await browser.click('[href="/page4"]')
|
||||
await browser.waitForElementByCss('[href="/page9"]').click()
|
||||
await browser.waitForElementByCss('[href="/page4"]').click()
|
||||
|
||||
await browser.waitForElementByCss('#onload-div-1')
|
||||
const sameText = await browser.elementById('onload-div-1').text()
|
||||
|
@ -233,12 +230,9 @@ const runTests = (isDev) => {
|
|||
browser = await webdriver(appPort, '/')
|
||||
|
||||
// Navigate away and back to page
|
||||
await browser.waitForElementByCss('[href="/page5"]')
|
||||
await browser.click('[href="/page5"]')
|
||||
await browser.waitForElementByCss('[href="/"]')
|
||||
await browser.click('[href="/"]')
|
||||
await browser.waitForElementByCss('[href="/page5"]')
|
||||
await browser.click('[href="/page5"]')
|
||||
await browser.waitForElementByCss('[href="/page5"]').click()
|
||||
await browser.waitForElementByCss('[href="/"]').click()
|
||||
await browser.waitForElementByCss('[href="/page5"]').click()
|
||||
|
||||
await browser.waitForElementByCss('.container')
|
||||
await waitFor(1000)
|
||||
|
@ -263,7 +257,7 @@ const runTests = (isDev) => {
|
|||
)
|
||||
const output = stdout + stderr
|
||||
|
||||
expect(output.replace(/\n|\r/g, '')).toMatch(
|
||||
expect(output.replace(/[\n\r]/g, '')).toMatch(
|
||||
/It looks like you're trying to use Partytown with next\/script but do not have the required package\(s\) installed.Please install Partytown by running:.*?(npm|pnpm|yarn) (install|add) (--save-dev|--dev) @builder.io\/partytownIf you are not trying to use Partytown, please disable the experimental "nextScriptWorkers" flag in next.config.js./
|
||||
)
|
||||
})
|
||||
|
@ -279,10 +273,8 @@ const runTests = (isDev) => {
|
|||
expect(text).toBe('aaa')
|
||||
|
||||
// Navigate to different page and back
|
||||
await browser.waitForElementByCss('[href="/page9"]')
|
||||
await browser.click('[href="/page9"]')
|
||||
await browser.waitForElementByCss('[href="/page8"]')
|
||||
await browser.click('[href="/page8"]')
|
||||
await browser.waitForElementByCss('[href="/page9"]').click()
|
||||
await browser.waitForElementByCss('[href="/page8"]').click()
|
||||
|
||||
await browser.waitForElementByCss('.container')
|
||||
const sameText = await browser.elementById('text').text()
|
||||
|
|
|
@ -2,13 +2,8 @@ export type Event = 'request'
|
|||
|
||||
/**
|
||||
* This is the base Browser interface all browser
|
||||
* classes should build off of, it is the bare
|
||||
* classes should build on, it is the bare
|
||||
* methods we aim to support across tests
|
||||
*
|
||||
* They will always await last executed command.
|
||||
* The interface is mutable - it doesn't have to be in sequence.
|
||||
*
|
||||
* You can manually await this interface to wait for completion of the last scheduled command.
|
||||
*/
|
||||
export abstract class BrowserInterface implements PromiseLike<any> {
|
||||
private promise?: Promise<any>
|
||||
|
@ -22,108 +17,76 @@ export abstract class BrowserInterface implements PromiseLike<any> {
|
|||
protected chain<T>(
|
||||
nextCall: (current: any) => T | PromiseLike<T>
|
||||
): BrowserInterface & Promise<T> {
|
||||
if (!this.promise) {
|
||||
this.promise = Promise.resolve(this)
|
||||
}
|
||||
this.promise = this.promise.then(nextCall)
|
||||
this.then = (...args) => this.promise.then(...args)
|
||||
this.catch = (...args) => this.promise.catch(...args)
|
||||
this.finally = (...args) => this.promise.finally(...args)
|
||||
return this
|
||||
}
|
||||
const promise = Promise.resolve(this.promise).then(nextCall)
|
||||
|
||||
/**
|
||||
* This function will run in chain - it will wait for previous commands.
|
||||
* But it won't have an effect on chain value and chain will still be green if this throws.
|
||||
*/
|
||||
protected chainWithReturnValue<T>(
|
||||
callback: (...args: any[]) => Promise<T>
|
||||
): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.chain(async (...args: any[]) => {
|
||||
try {
|
||||
resolve(await callback(...args))
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
function get(target: BrowserInterface, p: string | symbol): any {
|
||||
switch (p) {
|
||||
case 'promise':
|
||||
return promise
|
||||
case 'then':
|
||||
return promise.then.bind(promise)
|
||||
case 'catch':
|
||||
return promise.catch.bind(promise)
|
||||
case 'finally':
|
||||
return promise.finally.bind(promise)
|
||||
default:
|
||||
return target[p]
|
||||
}
|
||||
}
|
||||
|
||||
return new Proxy<any>(this, {
|
||||
get,
|
||||
})
|
||||
}
|
||||
|
||||
async setup(
|
||||
protected chainWithReturnValue<T>(
|
||||
callback: (value: any) => T | PromiseLike<T>
|
||||
): Promise<T> {
|
||||
return Promise.resolve(this.promise).then(callback)
|
||||
}
|
||||
|
||||
abstract setup(
|
||||
browserName: string,
|
||||
locale: string,
|
||||
javaScriptEnabled: boolean,
|
||||
ignoreHttpsErrors: boolean,
|
||||
headless: boolean
|
||||
): Promise<void> {}
|
||||
async close(): Promise<void> {}
|
||||
async quit(): Promise<void> {}
|
||||
): Promise<void>
|
||||
abstract close(): Promise<void>
|
||||
|
||||
elementsByCss(selector: string): BrowserInterface[] {
|
||||
return [this]
|
||||
}
|
||||
elementByCss(selector: string): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
elementById(selector: string): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
touchStart(): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
click(opts?: { modifierKey?: boolean }): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
keydown(key: string): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
keyup(key: string): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
focusPage(): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
type(text: string): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
moveTo(): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
abstract elementsByCss(selector: string): BrowserInterface[]
|
||||
abstract elementByCss(selector: string): BrowserInterface
|
||||
abstract elementById(selector: string): BrowserInterface
|
||||
abstract touchStart(): BrowserInterface
|
||||
abstract click(opts?: { modifierKey?: boolean }): BrowserInterface
|
||||
abstract keydown(key: string): BrowserInterface
|
||||
abstract keyup(key: string): BrowserInterface
|
||||
abstract type(text: string): BrowserInterface
|
||||
abstract moveTo(): BrowserInterface
|
||||
// TODO(NEXT-290): type this correctly as awaitable
|
||||
waitForElementByCss(selector: string, timeout?: number): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
waitForCondition(snippet: string, timeout?: number): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
abstract waitForElementByCss(
|
||||
selector: string,
|
||||
timeout?: number
|
||||
): BrowserInterface
|
||||
abstract waitForCondition(snippet: string, timeout?: number): BrowserInterface
|
||||
/**
|
||||
* Use browsers `go back` functionality.
|
||||
*/
|
||||
back(options?: any): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
abstract back(options?: any): BrowserInterface
|
||||
/**
|
||||
* Use browsers `go forward` functionality. Inverse of back.
|
||||
*/
|
||||
forward(options?: any): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
refresh(): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
setDimensions(opts: { height: number; width: number }): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
addCookie(opts: { name: string; value: string }): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
deleteCookies(): BrowserInterface {
|
||||
return this
|
||||
}
|
||||
on(event: Event, cb: (...args: any[]) => void) {}
|
||||
off(event: Event, cb: (...args: any[]) => void) {}
|
||||
async loadPage(
|
||||
abstract forward(options?: any): BrowserInterface
|
||||
abstract refresh(): BrowserInterface
|
||||
abstract setDimensions(opts: {
|
||||
height: number
|
||||
width: number
|
||||
}): BrowserInterface
|
||||
abstract addCookie(opts: { name: string; value: string }): BrowserInterface
|
||||
abstract deleteCookies(): BrowserInterface
|
||||
abstract on(event: Event, cb: (...args: any[]) => void): void
|
||||
abstract off(event: Event, cb: (...args: any[]) => void): void
|
||||
abstract loadPage(
|
||||
url: string,
|
||||
{
|
||||
disableCache,
|
||||
|
@ -136,44 +99,23 @@ export abstract class BrowserInterface implements PromiseLike<any> {
|
|||
beforePageLoad?: Function
|
||||
pushErrorAsConsoleLog?: boolean
|
||||
}
|
||||
): Promise<void> {}
|
||||
async get(url: string): Promise<void> {}
|
||||
): Promise<void>
|
||||
abstract get(url: string): Promise<void>
|
||||
|
||||
async getValue<T = any>(): Promise<T> {
|
||||
return
|
||||
}
|
||||
async getAttribute<T = any>(name: string): Promise<T> {
|
||||
return
|
||||
}
|
||||
async eval<T = any>(snippet: string | Function, ...args: any[]): Promise<T> {
|
||||
return
|
||||
}
|
||||
async evalAsync<T = any>(
|
||||
abstract getValue<T = any>(): Promise<T>
|
||||
abstract getAttribute<T = any>(name: string): Promise<T>
|
||||
abstract eval<T = any>(snippet: string | Function, ...args: any[]): Promise<T>
|
||||
abstract evalAsync<T = any>(
|
||||
snippet: string | Function,
|
||||
...args: any[]
|
||||
): Promise<T> {
|
||||
return
|
||||
}
|
||||
async text(): Promise<string> {
|
||||
return ''
|
||||
}
|
||||
async getComputedCss(prop: string): Promise<string> {
|
||||
return ''
|
||||
}
|
||||
async hasElementByCssSelector(selector: string): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
async log(): Promise<
|
||||
): Promise<T>
|
||||
abstract text(): Promise<string>
|
||||
abstract getComputedCss(prop: string): Promise<string>
|
||||
abstract hasElementByCssSelector(selector: string): Promise<boolean>
|
||||
abstract log(): Promise<
|
||||
{ source: 'error' | 'info' | 'log'; message: string }[]
|
||||
> {
|
||||
return []
|
||||
}
|
||||
async websocketFrames(): Promise<any[]> {
|
||||
return []
|
||||
}
|
||||
async url(): Promise<string> {
|
||||
return ''
|
||||
}
|
||||
|
||||
async waitForIdleNetwork(): Promise<void> {}
|
||||
>
|
||||
abstract websocketFrames(): Promise<any[]>
|
||||
abstract url(): Promise<string>
|
||||
abstract waitForIdleNetwork(): Promise<void>
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
Page,
|
||||
ElementHandle,
|
||||
devices,
|
||||
} from 'playwright-chromium'
|
||||
} from 'playwright'
|
||||
import path from 'path'
|
||||
|
||||
let page: Page
|
||||
|
@ -50,11 +50,7 @@ export class Playwright extends BrowserInterface {
|
|||
private eventCallbacks: Record<Event, Set<(...args: any[]) => void>> = {
|
||||
request: new Set(),
|
||||
}
|
||||
private async initContextTracing(
|
||||
url: string,
|
||||
page: Page,
|
||||
context: BrowserContext
|
||||
) {
|
||||
private async initContextTracing(url: string, context: BrowserContext) {
|
||||
if (!tracePlaywright) {
|
||||
return
|
||||
}
|
||||
|
@ -69,17 +65,13 @@ export class Playwright extends BrowserInterface {
|
|||
sources: true,
|
||||
})
|
||||
this.activeTrace = encodeURIComponent(url)
|
||||
|
||||
page.on('close', async () => {
|
||||
await teardown(this.teardownTracing.bind(this))
|
||||
})
|
||||
} catch (e) {
|
||||
this.activeTrace = undefined
|
||||
}
|
||||
}
|
||||
|
||||
private async teardownTracing() {
|
||||
if (!tracePlaywright || !this.activeTrace) {
|
||||
if (!this.activeTrace) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -163,6 +155,11 @@ export class Playwright extends BrowserInterface {
|
|||
contextHasJSEnabled = javaScriptEnabled
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await teardown(this.teardownTracing.bind(this))
|
||||
await page?.close()
|
||||
}
|
||||
|
||||
async launchBrowser(browserName: string, launchOptions: Record<string, any>) {
|
||||
if (browserName === 'safari') {
|
||||
return await webkit.launch(launchOptions)
|
||||
|
@ -201,13 +198,15 @@ export class Playwright extends BrowserInterface {
|
|||
beforePageLoad?: (...args: any[]) => void
|
||||
}
|
||||
) {
|
||||
await this.close()
|
||||
|
||||
// clean-up existing pages
|
||||
for (const oldPage of context.pages()) {
|
||||
await oldPage.close()
|
||||
}
|
||||
|
||||
await this.initContextTracing(url, context)
|
||||
page = await context.newPage()
|
||||
await this.initContextTracing(url, page, context)
|
||||
|
||||
// in development compilation can take longer due to
|
||||
// lower CPU availability in GH actions
|
||||
|
@ -221,7 +220,7 @@ export class Playwright extends BrowserInterface {
|
|||
console.log('browser log:', msg)
|
||||
pageLogs.push({ source: msg.type(), message: msg.text() })
|
||||
})
|
||||
page.on('crash', (page) => {
|
||||
page.on('crash', () => {
|
||||
console.error('page crashed')
|
||||
})
|
||||
page.on('pageerror', (error) => {
|
||||
|
|
|
@ -1,362 +0,0 @@
|
|||
import path from 'path'
|
||||
import resolveFrom from 'resolve-from'
|
||||
import { execSync } from 'child_process'
|
||||
import { Options as ChromeOptions } from 'selenium-webdriver/chrome'
|
||||
import { Options as SafariOptions } from 'selenium-webdriver/safari'
|
||||
import { Options as FireFoxOptions } from 'selenium-webdriver/firefox'
|
||||
import { Builder, By, ThenableWebDriver, until } from 'selenium-webdriver'
|
||||
|
||||
import { BrowserInterface } from './base'
|
||||
|
||||
const {
|
||||
BROWSERSTACK,
|
||||
BROWSERSTACK_USERNAME,
|
||||
BROWSERSTACK_ACCESS_KEY,
|
||||
CHROME_BIN,
|
||||
LEGACY_SAFARI,
|
||||
SKIP_LOCAL_SELENIUM_SERVER,
|
||||
} = process.env
|
||||
|
||||
if (process.env.ChromeWebDriver) {
|
||||
process.env.PATH = `${process.env.ChromeWebDriver}${path.delimiter}${process.env.PATH}`
|
||||
}
|
||||
|
||||
let seleniumServer: any
|
||||
let browserStackLocal: any
|
||||
let browser: ThenableWebDriver
|
||||
|
||||
export async function quit() {
|
||||
await Promise.all([
|
||||
browser?.quit(),
|
||||
new Promise<void>((resolve) => {
|
||||
browserStackLocal
|
||||
? browserStackLocal.killAllProcesses(() => resolve())
|
||||
: resolve()
|
||||
}),
|
||||
])
|
||||
seleniumServer?.kill()
|
||||
|
||||
browser = undefined
|
||||
browserStackLocal = undefined
|
||||
seleniumServer = undefined
|
||||
}
|
||||
|
||||
export class Selenium extends BrowserInterface {
|
||||
private browserName: string
|
||||
|
||||
// TODO: support setting locale
|
||||
async setup(
|
||||
browserName: string,
|
||||
locale: string,
|
||||
javaScriptEnabled: boolean,
|
||||
headless: boolean
|
||||
) {
|
||||
if (browser) return
|
||||
this.browserName = browserName
|
||||
|
||||
let capabilities = {}
|
||||
const isSafari = browserName === 'safari'
|
||||
const isFirefox = browserName === 'firefox'
|
||||
const isIE = browserName === 'internet explorer'
|
||||
const isBrowserStack = BROWSERSTACK
|
||||
const localSeleniumServer = SKIP_LOCAL_SELENIUM_SERVER !== 'true'
|
||||
|
||||
// install conditional packages globally so the entire
|
||||
// monorepo doesn't need to rebuild when testing
|
||||
let globalNodeModules: string
|
||||
|
||||
if (isBrowserStack || localSeleniumServer) {
|
||||
globalNodeModules = execSync('npm root -g').toString().trim()
|
||||
}
|
||||
|
||||
if (isBrowserStack) {
|
||||
const { Local } = require(resolveFrom(
|
||||
globalNodeModules,
|
||||
'browserstack-local'
|
||||
))
|
||||
browserStackLocal = new Local()
|
||||
|
||||
const localBrowserStackOpts = {
|
||||
key: process.env.BROWSERSTACK_ACCESS_KEY,
|
||||
// Add a unique local identifier to run parallel tests
|
||||
// on BrowserStack
|
||||
localIdentifier: new Date().getTime(),
|
||||
}
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
browserStackLocal.start(localBrowserStackOpts, (err) => {
|
||||
if (err) return reject(err)
|
||||
console.log(
|
||||
'Started BrowserStackLocal',
|
||||
browserStackLocal.isRunning()
|
||||
)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
const safariOpts = {
|
||||
os: 'OS X',
|
||||
os_version: 'Mojave',
|
||||
browser: 'Safari',
|
||||
}
|
||||
const safariLegacyOpts = {
|
||||
os: 'OS X',
|
||||
os_version: 'Sierra',
|
||||
browserName: 'Safari',
|
||||
browser_version: '10.1',
|
||||
}
|
||||
const ieOpts = {
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'IE',
|
||||
}
|
||||
const firefoxOpts = {
|
||||
os: 'Windows',
|
||||
os_version: '10',
|
||||
browser: 'Firefox',
|
||||
}
|
||||
const sharedOpts = {
|
||||
'browserstack.local': true,
|
||||
'browserstack.video': false,
|
||||
'browserstack.user': BROWSERSTACK_USERNAME,
|
||||
'browserstack.key': BROWSERSTACK_ACCESS_KEY,
|
||||
'browserstack.localIdentifier': localBrowserStackOpts.localIdentifier,
|
||||
}
|
||||
|
||||
capabilities = {
|
||||
...capabilities,
|
||||
...sharedOpts,
|
||||
|
||||
...(isIE ? ieOpts : {}),
|
||||
...(isSafari ? (LEGACY_SAFARI ? safariLegacyOpts : safariOpts) : {}),
|
||||
...(isFirefox ? firefoxOpts : {}),
|
||||
}
|
||||
} else if (localSeleniumServer) {
|
||||
console.log('Installing selenium server')
|
||||
const seleniumServerMod = require(resolveFrom(
|
||||
globalNodeModules,
|
||||
'selenium-standalone'
|
||||
))
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
seleniumServerMod.install((err) => {
|
||||
if (err) return reject(err)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
|
||||
console.log('Starting selenium server')
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
seleniumServerMod.start((err, child) => {
|
||||
if (err) return reject(err)
|
||||
seleniumServer = child
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
console.log('Started selenium server')
|
||||
}
|
||||
|
||||
let chromeOptions = new ChromeOptions()
|
||||
let firefoxOptions = new FireFoxOptions()
|
||||
let safariOptions = new SafariOptions()
|
||||
|
||||
if (headless) {
|
||||
const screenSize = { width: 1280, height: 720 }
|
||||
chromeOptions = chromeOptions.headless().windowSize(screenSize) as any
|
||||
firefoxOptions = firefoxOptions.headless().windowSize(screenSize)
|
||||
}
|
||||
|
||||
if (CHROME_BIN) {
|
||||
chromeOptions = chromeOptions.setChromeBinaryPath(
|
||||
path.resolve(CHROME_BIN)
|
||||
)
|
||||
}
|
||||
|
||||
let seleniumServerUrl
|
||||
|
||||
if (isBrowserStack) {
|
||||
seleniumServerUrl = 'http://hub-cloud.browserstack.com/wd/hub'
|
||||
} else if (localSeleniumServer) {
|
||||
seleniumServerUrl = `http://localhost:4444/wd/hub`
|
||||
}
|
||||
|
||||
browser = new Builder()
|
||||
.usingServer(seleniumServerUrl)
|
||||
.withCapabilities(capabilities)
|
||||
.forBrowser(browserName)
|
||||
.setChromeOptions(chromeOptions)
|
||||
.setFirefoxOptions(firefoxOptions)
|
||||
.setSafariOptions(safariOptions)
|
||||
.build()
|
||||
}
|
||||
|
||||
async get(url: string): Promise<void> {
|
||||
return browser.get(url)
|
||||
}
|
||||
|
||||
async loadPage(url: string) {
|
||||
// in chrome we use a new tab for testing
|
||||
if (this.browserName === 'chrome') {
|
||||
const initialHandle = await browser.getWindowHandle()
|
||||
|
||||
await browser.switchTo().newWindow('tab')
|
||||
const newHandle = await browser.getWindowHandle()
|
||||
|
||||
await browser.switchTo().window(initialHandle)
|
||||
await browser.close()
|
||||
await browser.switchTo().window(newHandle)
|
||||
|
||||
// clean-up extra windows created from links and such
|
||||
for (const handle of await browser.getAllWindowHandles()) {
|
||||
if (handle !== newHandle) {
|
||||
await browser.switchTo().window(handle)
|
||||
await browser.close()
|
||||
}
|
||||
}
|
||||
await browser.switchTo().window(newHandle)
|
||||
} else {
|
||||
await browser.get('about:blank')
|
||||
}
|
||||
return browser.get(url)
|
||||
}
|
||||
|
||||
back(): BrowserInterface {
|
||||
return this.chain(() => {
|
||||
return browser.navigate().back()
|
||||
})
|
||||
}
|
||||
forward(): BrowserInterface {
|
||||
return this.chain(() => {
|
||||
return browser.navigate().forward()
|
||||
})
|
||||
}
|
||||
refresh(): BrowserInterface {
|
||||
return this.chain(() => {
|
||||
return browser.navigate().refresh()
|
||||
})
|
||||
}
|
||||
setDimensions({
|
||||
width,
|
||||
height,
|
||||
}: {
|
||||
height: number
|
||||
width: number
|
||||
}): BrowserInterface {
|
||||
return this.chain(() =>
|
||||
browser.manage().window().setRect({ width, height, x: 0, y: 0 })
|
||||
)
|
||||
}
|
||||
addCookie(opts: { name: string; value: string }): BrowserInterface {
|
||||
return this.chain(() => browser.manage().addCookie(opts))
|
||||
}
|
||||
deleteCookies(): BrowserInterface {
|
||||
return this.chain(() => browser.manage().deleteAllCookies())
|
||||
}
|
||||
|
||||
elementByCss(selector: string) {
|
||||
return this.chain(() => {
|
||||
return browser.findElement(By.css(selector)).then((el: any) => {
|
||||
el.selector = selector
|
||||
el.text = () => el.getText()
|
||||
el.getComputedCss = (prop) => el.getCssValue(prop)
|
||||
el.type = (text) => el.sendKeys(text)
|
||||
el.getValue = () =>
|
||||
browser.executeScript(
|
||||
`return document.querySelector('${selector}').value`
|
||||
)
|
||||
return el
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
elementById(sel) {
|
||||
return this.elementByCss(`#${sel}`)
|
||||
}
|
||||
|
||||
getValue() {
|
||||
return this.chain((el) =>
|
||||
browser.executeScript(
|
||||
`return document.querySelector('${el.selector}').value`
|
||||
)
|
||||
) as any
|
||||
}
|
||||
|
||||
text() {
|
||||
return this.chain((el) => el.getText()) as any
|
||||
}
|
||||
|
||||
type(text) {
|
||||
return this.chain((el) => el.sendKeys(text))
|
||||
}
|
||||
|
||||
moveTo() {
|
||||
return this.chain((el) => {
|
||||
return browser
|
||||
.actions()
|
||||
.move({ origin: el })
|
||||
.perform()
|
||||
.then(() => el)
|
||||
})
|
||||
}
|
||||
|
||||
async getComputedCss(prop: string) {
|
||||
return this.chain((el) => {
|
||||
return el.getCssValue(prop)
|
||||
}) as any
|
||||
}
|
||||
|
||||
async getAttribute<T = any>(attr) {
|
||||
return this.chain((el) => el.getAttribute(attr)) as T
|
||||
}
|
||||
|
||||
async hasElementByCssSelector(selector: string) {
|
||||
return this.eval(`!!document.querySelector('${selector}')`) as any
|
||||
}
|
||||
|
||||
click() {
|
||||
return this.chain((el) => {
|
||||
return el.click().then(() => el)
|
||||
})
|
||||
}
|
||||
|
||||
elementsByCss(sel) {
|
||||
return this.chain(() =>
|
||||
browser.findElements(By.css(sel))
|
||||
) as any as BrowserInterface[]
|
||||
}
|
||||
|
||||
waitForElementByCss(sel, timeout) {
|
||||
return this.chain(() =>
|
||||
browser.wait(until.elementLocated(By.css(sel)), timeout)
|
||||
)
|
||||
}
|
||||
|
||||
waitForCondition(condition, timeout) {
|
||||
return this.chain(() =>
|
||||
browser.wait(async (driver) => {
|
||||
return driver.executeScript('return ' + condition).catch(() => false)
|
||||
}, timeout)
|
||||
)
|
||||
}
|
||||
|
||||
async eval<T = any>(snippet) {
|
||||
if (typeof snippet === 'string' && !snippet.startsWith('return')) {
|
||||
snippet = `return ${snippet}`
|
||||
}
|
||||
return browser.executeScript<T>(snippet)
|
||||
}
|
||||
|
||||
async evalAsync<T = any>(snippet) {
|
||||
if (typeof snippet === 'string' && !snippet.startsWith('return')) {
|
||||
snippet = `return ${snippet}`
|
||||
}
|
||||
return browser.executeAsyncScript<T>(snippet)
|
||||
}
|
||||
|
||||
async log() {
|
||||
return this.chain(() => browser.manage().logs().get('browser')) as any
|
||||
}
|
||||
|
||||
async url() {
|
||||
return this.chain(() => browser.getCurrentUrl()) as any
|
||||
}
|
||||
}
|
|
@ -284,6 +284,9 @@ export function nextTestSetup(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `nextTestSetup` directly.
|
||||
*/
|
||||
export function createNextDescribe(
|
||||
name: string,
|
||||
options: Parameters<typeof createNext>[0] & {
|
||||
|
|
|
@ -26,22 +26,21 @@ if (isBrowserStack) {
|
|||
}
|
||||
}
|
||||
|
||||
let browserQuit: () => Promise<void>
|
||||
let browserTeardown: (() => Promise<void>)[] = []
|
||||
let browserQuit: (() => Promise<void>) | undefined
|
||||
|
||||
if (typeof afterAll === 'function') {
|
||||
afterAll(async () => {
|
||||
await Promise.all(browserTeardown.map((f) => f())).catch((e) =>
|
||||
console.error('browser teardown', e)
|
||||
)
|
||||
|
||||
if (browserQuit) {
|
||||
await browserQuit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const USE_SELENIUM = Boolean(
|
||||
process.env.LEGACY_SAFARI ||
|
||||
process.env.BROWSER_NAME === 'internet explorer' ||
|
||||
process.env.SKIP_LOCAL_SELENIUM_SERVER
|
||||
)
|
||||
|
||||
/**
|
||||
*
|
||||
* @param appPortOrUrl can either be the port or the full URL
|
||||
|
@ -94,11 +93,7 @@ export default async function webdriver(
|
|||
} = options
|
||||
|
||||
// we import only the needed interface
|
||||
if (USE_SELENIUM) {
|
||||
const { Selenium, quit } = await import('./browsers/selenium')
|
||||
CurrentInterface = Selenium
|
||||
browserQuit = quit
|
||||
} else if (
|
||||
if (
|
||||
process.env.RECORD_REPLAY === 'true' ||
|
||||
process.env.RECORD_REPLAY === '1'
|
||||
) {
|
||||
|
@ -139,6 +134,8 @@ export default async function webdriver(
|
|||
})
|
||||
console.log(`\n> Loaded browser with ${fullUrl}\n`)
|
||||
|
||||
browserTeardown.push(browser.close.bind(browser))
|
||||
|
||||
// Wait for application to hydrate
|
||||
if (waitHydration) {
|
||||
console.log(`\n> Waiting hydration for ${fullUrl}\n`)
|
||||
|
@ -198,3 +195,5 @@ export default async function webdriver(
|
|||
}
|
||||
return browser
|
||||
}
|
||||
|
||||
export { BrowserInterface }
|
||||
|
|
|
@ -715,8 +715,9 @@ createNextDescribe(
|
|||
// @ts-expect-error Exists on window
|
||||
window.__DATA_BE_GONE = 'true'
|
||||
})
|
||||
await browser.waitForElementByCss('#to-nonexistent-page')
|
||||
await browser.click('#to-nonexistent-page')
|
||||
await browser
|
||||
.waitForElementByCss('#to-nonexistent-page')
|
||||
.click('#to-nonexistent-page')
|
||||
await browser.waitForElementByCss('.about-page')
|
||||
|
||||
const oldData = await browser.eval(`window.__DATA_BE_GONE`)
|
||||
|
|
Loading…
Reference in a new issue