/
webkit.ts
148 lines (118 loc) · 5.23 KB
/
webkit.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import Debug from 'debug'
import { EventEmitter } from 'events'
import type playwright from 'playwright-webkit'
import type { Browser, BrowserInstance } from './types'
import type { Automation } from '../automation'
import { WebKitAutomation } from './webkit-automation'
import * as unhandledExceptions from '../unhandled_exceptions'
import type { BrowserLaunchOpts, BrowserNewTabOpts } from '@packages/types'
import utils from './utils'
const debug = Debug('cypress:server:browsers:webkit')
let wkAutomation: WebKitAutomation | undefined
export async function connectToNewSpec (browser: Browser, options: BrowserNewTabOpts, automation: Automation) {
if (!wkAutomation) throw new Error('connectToNewSpec called without wkAutomation')
automation.use(wkAutomation)
wkAutomation.automation = automation
await options.onInitializeNewBrowserTab()
await wkAutomation.reset({
newUrl: options.url,
downloadsFolder: options.downloadsFolder,
videoApi: options.videoApi,
})
}
/**
* Clear instance state for the webkit instance, this is normally called in on kill or on exit.
*/
export function clearInstanceState () {
wkAutomation = undefined
}
export function connectToExisting () {
throw new Error('Cypress-in-Cypress is not supported for WebKit.')
}
/**
* Playwright adds an `exit` event listener to run a cleanup process. It tries to use the current binary to run a Node script by passing it as argv[1].
* However, the Electron binary does not support an entrypoint, leading Cypress to think it's being opened in global mode (no args) when this fn is called.
* Solution is to filter out the problematic function.
* TODO(webkit): do we want to run this cleanup script another way?
* @see https://github.com/microsoft/playwright/blob/7e2aec7454f596af452b51a2866e86370291ac8b/packages/playwright-core/src/utils/processLauncher.ts#L191-L203
*/
function removeBadExitListener () {
const killProcessAndCleanup = process.rawListeners('exit').find((fn) => fn.name === 'killProcessAndCleanup')
// @ts-expect-error Electron's Process types override those of @types/node, leading to `exit` not being recognized as an event
if (killProcessAndCleanup) process.removeListener('exit', killProcessAndCleanup)
else debug('did not find killProcessAndCleanup, which may cause interactive mode to unexpectedly open')
}
export async function open (browser: Browser, url: string, options: BrowserLaunchOpts, automation: Automation): Promise<BrowserInstance> {
if (!options.experimentalWebKitSupport) {
throw new Error('WebKit was launched, but the experimental feature was not enabled. Please add `experimentalWebKitSupport: true` to your config file to launch WebKit.')
}
// resolve pw from user's project path
const pwModulePath = require.resolve('playwright-webkit', { paths: [process.cwd()] })
let pw: typeof playwright
try {
pw = await import(pwModulePath)
} catch (err) {
err.message = `There was an error importing \`playwright-webkit\`, is it installed?\n\nError text: ${err.stack}`
throw err
}
const defaultLaunchOptions = {
preferences: {
proxy: {
server: options.proxyServer,
},
headless: browser.isHeadless,
},
extensions: [],
args: [],
env: {},
}
const launchOptions = await utils.executeBeforeBrowserLaunch(browser, defaultLaunchOptions, options)
if (launchOptions.extensions.length) options.onWarning?.(new Error('WebExtensions not supported in WebKit, but extensions were passed in before:browser:launch.'))
launchOptions.preferences.args = [...launchOptions.args, ...(launchOptions.preferences.args || [])]
let pwServer: playwright.BrowserServer
try {
pwServer = await pw.webkit.launchServer(launchOptions.preferences)
} catch (err) {
err.message = `There was an error launching \`playwright-webkit\`: \n\n\`\`\`${err.message}\n\`\`\``
throw err
}
removeBadExitListener()
const pwBrowser = await pw.webkit.connect(pwServer.wsEndpoint())
wkAutomation = await WebKitAutomation.create({
automation,
browser: pwBrowser,
initialUrl: url,
downloadsFolder: options.downloadsFolder,
shouldMarkAutIframeRequests: !!options.experimentalSessionAndOrigin,
videoApi: options.videoApi,
})
automation.use(wkAutomation)
class WkInstance extends EventEmitter implements BrowserInstance {
pid = pwServer.process().pid
constructor () {
super()
pwBrowser.on('disconnected', () => {
debug('pwBrowser disconnected')
this.emit('exit')
})
this.suppressUnhandledEconnreset()
}
async kill () {
debug('closing pwBrowser')
await pwBrowser.close()
clearInstanceState()
}
/**
* An unhandled `read ECONNRESET` in the depths of `playwright-webkit` is causing the process to crash when running kitchensink on Linux. Absent a
* way to attach to the `error` event, this replaces the global `unhandledException` handler with one that will not exit the process on ECONNRESET.
*/
private suppressUnhandledEconnreset () {
unhandledExceptions.handle((err: NodeJS.ErrnoException) => {
return err.code === 'ECONNRESET'
})
// restore normal exception handling behavior
this.once('exit', () => unhandledExceptions.handle())
}
}
return new WkInstance()
}