-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New API #76
New API #76
Changes from 24 commits
e032b14
1b7448d
4cc1858
b528f37
d925ee2
ebb7f66
44c4eee
18e59b5
f409bbb
bf4f3c4
1fb910b
c60e99d
6701e8a
d6ae7e5
83d5fbf
87500ad
4847b5d
ba2dee5
b39ed95
fc95666
3d71a33
25696ee
0cdef51
211f6ca
1c6086b
d56bb0d
9d1274a
8fc3f52
96e40fe
74353da
85f898c
c46e2b2
bc8bc3b
67208bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,9 @@ | ||||||
/* eslint-disable no-console */ | ||||||
import NodeEnvironment from 'jest-environment-node' | ||||||
import { Config as JestConfig } from '@jest/types' | ||||||
import Expect = jest.Expect | ||||||
import playwright, { Browser, BrowserContext, Page } from 'playwright' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please import types from playwright-core |
||||||
|
||||||
import { | ||||||
checkBrowserEnv, | ||||||
checkDeviceEnv, | ||||||
|
@@ -9,8 +12,16 @@ import { | |||||
getPlaywrightInstance, | ||||||
readConfig, | ||||||
} from './utils' | ||||||
import { Config, CHROMIUM, GenericBrowser } from './constants' | ||||||
import playwright, { Browser } from 'playwright-core' | ||||||
import { | ||||||
Config, | ||||||
CHROMIUM, | ||||||
BrowserType, | ||||||
Initializer, | ||||||
InitializerProps, | ||||||
Args, | ||||||
RootProxy, | ||||||
GenericBrowser, | ||||||
} from './constants' | ||||||
|
||||||
const handleError = (error: Error): void => { | ||||||
process.emit('uncaughtException', error) | ||||||
|
@@ -67,7 +78,7 @@ const getBrowserPerProcess = async ( | |||||
// https://github.com/mmarkelov/jest-playwright/issues/42#issuecomment-589170220 | ||||||
if (browserType !== CHROMIUM && launchBrowserApp && launchBrowserApp.args) { | ||||||
launchBrowserApp.args = launchBrowserApp.args.filter( | ||||||
(item) => item !== '--no-sandbox', | ||||||
(item: string) => item !== '--no-sandbox', | ||||||
) | ||||||
} | ||||||
|
||||||
|
@@ -77,7 +88,7 @@ const getBrowserPerProcess = async ( | |||||
browserPerProcess = await playwrightInstance.launch(launchBrowserApp) | ||||||
} | ||||||
} | ||||||
return browserPerProcess | ||||||
return browserPerProcess as Browser | ||||||
} | ||||||
|
||||||
class PlaywrightEnvironment extends NodeEnvironment { | ||||||
|
@@ -90,15 +101,211 @@ class PlaywrightEnvironment extends NodeEnvironment { | |||||
async setup(): Promise<void> { | ||||||
resetBrowserCloseWatchdog() | ||||||
const config = await readConfig(this._config.rootDir) | ||||||
const browserType = getBrowserType(config) | ||||||
checkBrowserEnv(browserType) | ||||||
const { context, server, selectors } = config | ||||||
const device = getDeviceType(config) | ||||||
const playwrightInstance = await getPlaywrightInstance( | ||||||
browserType, | ||||||
selectors, | ||||||
) | ||||||
let contextOptions = context | ||||||
const { context, server, selectors, browsers, devices } = config | ||||||
// Two possible cases | ||||||
// browsers are defined | ||||||
if (config.USE_NEW_API && browsers && browsers.length) { | ||||||
// Playwright instances for each browser | ||||||
const playwrightInstances = await Promise.all( | ||||||
browsers.map((browser) => getPlaywrightInstance(browser, selectors)), | ||||||
) | ||||||
|
||||||
// Helpers | ||||||
const getResult = <T>( | ||||||
data: T[], | ||||||
instances: BrowserType[] | Array<keyof typeof playwright.devices>, | ||||||
) => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
const result: any = {} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
data.forEach((item: T, index: number) => { | ||||||
result[instances[index]] = item | ||||||
}) | ||||||
return result | ||||||
} | ||||||
|
||||||
const initialize = async <T>( | ||||||
browser: BrowserType, | ||||||
initializer: Initializer, | ||||||
): Promise<T> => { | ||||||
if (devices && devices.length) { | ||||||
return await Promise.all( | ||||||
devices.map((device) => initializer({ browser, device })), | ||||||
).then((data) => getResult<T>(data, devices)) | ||||||
} else { | ||||||
return initializer({ browser }) | ||||||
} | ||||||
} | ||||||
|
||||||
// Browsers | ||||||
const playwrightBrowsers = await Promise.all( | ||||||
browsers.map((browser, index) => | ||||||
getBrowserPerProcess(playwrightInstances[index], { | ||||||
...config, | ||||||
browser, | ||||||
}), | ||||||
), | ||||||
).then((data) => getResult(data, browsers)) | ||||||
|
||||||
// Contexts | ||||||
const contextInitializer = ({ | ||||||
browser, | ||||||
device, | ||||||
}: InitializerProps): Promise<BrowserContext> => { | ||||||
let contextOptions = {} | ||||||
if (device) { | ||||||
const { viewport, userAgent } = playwright.devices[device] | ||||||
contextOptions = { viewport, userAgent } | ||||||
} | ||||||
return playwrightBrowsers[browser].newContext(contextOptions) | ||||||
} | ||||||
|
||||||
const contexts = await Promise.all( | ||||||
browsers.map((browser) => initialize(browser, contextInitializer)), | ||||||
).then((data) => getResult(data, browsers)) | ||||||
|
||||||
// Pages | ||||||
const pageInitializer = ({ | ||||||
browser, | ||||||
device, | ||||||
}: InitializerProps): Promise<Page> => { | ||||||
const instance = contexts[browser] | ||||||
return device ? instance[device].newPage() : instance.newPage() | ||||||
} | ||||||
|
||||||
const pages = await Promise.all( | ||||||
browsers.map((browser) => initialize<Page>(browser, pageInitializer)), | ||||||
).then((data) => getResult(data, browsers)) | ||||||
|
||||||
const checker = <T>({ | ||||||
instance, | ||||||
key, | ||||||
args, | ||||||
}: { | ||||||
instance: any | ||||||
key: keyof T | ||||||
args: Args | ||||||
}) => { | ||||||
if (typeof instance[key] === 'function') { | ||||||
return ((instance[key] as unknown) as Function).call( | ||||||
instance, | ||||||
...args, | ||||||
) | ||||||
} else { | ||||||
return instance[key] | ||||||
} | ||||||
} | ||||||
|
||||||
// TODO Improve types | ||||||
const callAsync = async <T>( | ||||||
instances: RootProxy, | ||||||
key: keyof T, | ||||||
...args: Args | ||||||
) => | ||||||
await Promise.all( | ||||||
browsers.map(async (browser) => { | ||||||
const browserInstance: { | ||||||
[key: string]: T | ||||||
} = instances[browser] | ||||||
if (devices && devices.length) { | ||||||
return await Promise.all( | ||||||
devices.map((device) => { | ||||||
const instance = browserInstance[device] | ||||||
return checker<T>({ instance, key, args }) | ||||||
}), | ||||||
).then((data) => getResult(data, devices)) | ||||||
} else { | ||||||
return checker<T>({ instance: browserInstance, key, args }) | ||||||
} | ||||||
}), | ||||||
).then((data) => getResult(data, browsers)) | ||||||
|
||||||
const proxyWrapper = <T>(instances: RootProxy) => | ||||||
new Proxy( | ||||||
{}, | ||||||
{ | ||||||
get: (obj, key) => { | ||||||
const browser = browsers.find((item) => item === key) | ||||||
if (browser) { | ||||||
return instances[browser] | ||||||
} else { | ||||||
return (...args: Args) => | ||||||
callAsync<T>(instances, key as keyof T, ...args) | ||||||
} | ||||||
}, | ||||||
}, | ||||||
) | ||||||
|
||||||
const testRunner = ({ | ||||||
expectFunction, | ||||||
errorMessage, | ||||||
}: { | ||||||
expectFunction: Expect | ||||||
errorMessage: string | ||||||
}) => { | ||||||
try { | ||||||
return expectFunction | ||||||
} catch (e) { | ||||||
// TODO Think about error message | ||||||
console.log(errorMessage) | ||||||
return expectFunction | ||||||
} | ||||||
} | ||||||
|
||||||
this.global.browser = proxyWrapper<Browser>(playwrightBrowsers) | ||||||
this.global.context = proxyWrapper<BrowserContext>(contexts) | ||||||
this.global.page = proxyWrapper<Page>(pages) | ||||||
// TODO Types | ||||||
// TODO Add expectWebkit, expectFirefox? | ||||||
this.global.expectAllBrowsers = (input: any) => | ||||||
new Proxy( | ||||||
{}, | ||||||
{ | ||||||
get: (obj, key) => { | ||||||
const { expect } = this.global | ||||||
return (...args: Args) => { | ||||||
browsers.forEach((browser) => { | ||||||
if (devices && devices.length) { | ||||||
devices.forEach((device) => { | ||||||
const expectFunction = expect(input[browser][device])[ | ||||||
key | ||||||
](...args) | ||||||
const errorMessage = `Failed test for ${browser}, ${device}` | ||||||
testRunner({ expectFunction, errorMessage }) | ||||||
}) | ||||||
} else { | ||||||
const expectFunction = expect(input[browser])[key](...args) | ||||||
const errorMessage = `Failed test for ${browser}` | ||||||
testRunner({ expectFunction, errorMessage }) | ||||||
} | ||||||
}) | ||||||
} | ||||||
}, | ||||||
}, | ||||||
) | ||||||
} else { | ||||||
// Browsers are not defined | ||||||
const browserType = getBrowserType(config) | ||||||
checkBrowserEnv(browserType) | ||||||
const device = getDeviceType(config) | ||||||
const playwrightInstance = await getPlaywrightInstance( | ||||||
browserType, | ||||||
selectors, | ||||||
) | ||||||
let contextOptions = context | ||||||
const availableDevices = Object.keys(playwright.devices) | ||||||
if (device) { | ||||||
checkDeviceEnv(device, availableDevices) | ||||||
const { viewport, userAgent } = playwright.devices[device] | ||||||
contextOptions = { viewport, userAgent, ...contextOptions } | ||||||
} | ||||||
this.global.browser = await getBrowserPerProcess( | ||||||
playwrightInstance, | ||||||
config, | ||||||
) | ||||||
this.global.context = await this.global.browser.newContext(contextOptions) | ||||||
this.global.page = await this.global.context.newPage() | ||||||
} | ||||||
|
||||||
this.global.page.on('pageerror', handleError) | ||||||
|
||||||
if (server) { | ||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||||||
|
@@ -124,16 +331,6 @@ class PlaywrightEnvironment extends NodeEnvironment { | |||||
} | ||||||
} | ||||||
|
||||||
const availableDevices = Object.keys(playwright.devices) | ||||||
if (device) { | ||||||
checkDeviceEnv(device, availableDevices) | ||||||
const { viewport, userAgent } = playwright.devices[device] | ||||||
contextOptions = { viewport, userAgent, ...contextOptions } | ||||||
} | ||||||
this.global.browser = await getBrowserPerProcess(playwrightInstance, config) | ||||||
this.global.context = await this.global.browser.newContext(contextOptions) | ||||||
this.global.page = await this.global.context.newPage() | ||||||
this.global.page.on('pageerror', handleError) | ||||||
this.global.jestPlaywright = { | ||||||
debug: async (): Promise<void> => { | ||||||
// Run a debugger (in case Playwright has been launched with `{ devtools: true }`) | ||||||
|
@@ -181,9 +378,10 @@ class PlaywrightEnvironment extends NodeEnvironment { | |||||
if (!jestConfig.watch && !jestConfig.watchAll && teardownServer) { | ||||||
await teardownServer() | ||||||
} | ||||||
if (this.global.page) { | ||||||
this.global.page.removeListener('pageerror', handleError) | ||||||
await this.global.page.close() | ||||||
const { page } = this.global | ||||||
if (page) { | ||||||
page.removeListener('pageerror', handleError) | ||||||
await page.close() | ||||||
} | ||||||
startBrowserCloseWatchdog() | ||||||
} | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.