Skip to content
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

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e032b14
New API
mmarkelov Mar 19, 2020
1b7448d
Changes
mmarkelov Mar 19, 2020
4cc1858
Do not rubbish global with unnecessary variables
mmarkelov Mar 19, 2020
b528f37
Adding browsers and contexts to global
mmarkelov Mar 19, 2020
d925ee2
Replace array with object
mmarkelov Mar 19, 2020
ebb7f66
Pass instances to proxy
mmarkelov Mar 20, 2020
44c4eee
Fix instances
mmarkelov Mar 20, 2020
18e59b5
Add expectAllBrowsers helper
mmarkelov Mar 20, 2020
f409bbb
Improve some types
mmarkelov Mar 21, 2020
bf4f3c4
First step for support devices throw config
mmarkelov Mar 21, 2020
1fb910b
Update packages
mmarkelov Mar 21, 2020
c60e99d
Refactoring logic
mmarkelov Mar 22, 2020
6701e8a
Support config without devices
mmarkelov Mar 22, 2020
d6ae7e5
Some typescript improvements
mmarkelov Mar 23, 2020
83d5fbf
Fix some others types
mmarkelov Mar 23, 2020
87500ad
TS common types
mmarkelov Mar 23, 2020
4847b5d
Some changes
mmarkelov Mar 24, 2020
ba2dee5
Merge branches 'New-API' and 'master' of https://github.com/mmarkelov…
mmarkelov Mar 27, 2020
b39ed95
Update dependencies
mmarkelov Mar 27, 2020
fc95666
Fix merging master
mmarkelov Mar 27, 2020
3d71a33
Merge branch 'master' of https://github.com/mmarkelov/jest-playwright…
mmarkelov Mar 29, 2020
25696ee
Merge master
mmarkelov Mar 29, 2020
0cdef51
Merge branches 'New-API' and 'master' of https://github.com/mmarkelov…
mmarkelov Apr 9, 2020
211f6ca
Add prop to support new API
mmarkelov Apr 9, 2020
1c6086b
Using import from playwright-core
mmarkelov Apr 10, 2020
d56bb0d
Merge branches 'New-API' and 'master' of https://github.com/mmarkelov…
mmarkelov Apr 12, 2020
9d1274a
Rewrite selectors initialization
mmarkelov Apr 12, 2020
8fc3f52
Fixed error message
mmarkelov Apr 15, 2020
96e40fe
Using new API to e2e test
mmarkelov Apr 15, 2020
74353da
Fix tests
mmarkelov Apr 15, 2020
85f898c
Fix tests
mmarkelov Apr 17, 2020
c46e2b2
Merge branch 'master' of https://github.com/mmarkelov/jest-playwright…
mmarkelov Apr 17, 2020
bc8bc3b
Fix tests
mmarkelov Apr 17, 2020
67208bb
Some ts fixes
mmarkelov Apr 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 224 additions & 26 deletions src/PlaywrightEnvironment.ts
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import Expect = jest.Expect

import playwright, { Browser, BrowserContext, Page } from 'playwright'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please import types from playwright-core


import {
checkBrowserEnv,
checkDeviceEnv,
Expand All @@ -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)
Expand Down Expand Up @@ -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',
)
}

Expand All @@ -77,7 +88,7 @@ const getBrowserPerProcess = async (
browserPerProcess = await playwrightInstance.launch(launchBrowserApp)
}
}
return browserPerProcess
return browserPerProcess as Browser
}

class PlaywrightEnvironment extends NodeEnvironment {
Expand All @@ -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>,
) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
) => {
): any => {

const result: any = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const result: any = {}
const result: any = {} // eslint-disable-line no-explicit-any

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
Expand All @@ -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 }`)
Expand Down Expand Up @@ -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()
}
Expand Down
14 changes: 14 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export interface Config {
server?: JestDevServerOptions
selectors?: SelectorType[]
connectBrowserApp?: BrowserTypeConnectOptions
USE_NEW_API?: boolean
}

export const DEFAULT_CONFIG: Config = {
Expand All @@ -47,3 +48,16 @@ export const DEFAULT_CONFIG: Config = {
browser: CHROMIUM,
exitOnPageError: true,
}

// Utils
export type InitializerProps = {
browser: BrowserType
device?: string
}

export type RootProxy = {
[key: string]: any
}

export type Initializer = (args: InitializerProps) => Promise<any>
export type Args = (string | Function)[]