-
-
Notifications
You must be signed in to change notification settings - Fork 751
Expand file tree
/
Copy pathexpose.js
More file actions
159 lines (149 loc) · 5 KB
/
Copy pathexpose.js
File metadata and controls
159 lines (149 loc) · 5 KB
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
149
150
151
152
153
154
155
156
157
158
159
import Container from '../container.js'
const RESERVED_NAMES = new Set(['I', 'test', 'suite'])
const SHORTHAND_PROPERTIES = new Set(['page', 'browser', 'browserContext', 'context'])
const defaultConfig = {
inject: {},
}
/**
* Exposes properties from helper instances as injectable test arguments.
* Use it to access the underlying Playwright/Puppeteer `page`, the wdio `browser` client,
* or any other helper internal directly from a Scenario:
*
* ```js
* Scenario('listen for requests', async ({ I, page, browser }) => {
* page.on('request', r => console.log(r.url()))
* await page.evaluate(() => 1 + 1)
* I.amOnPage('/')
* })
* ```
*
* The injected value is a live proxy: every property access reads the *current*
* helper property, so mid-test reassignments (popups, `switchToNextTab`,
* `openNewTab`) are reflected automatically. Calls are not wrapped as
* CodeceptJS steps — `await page.evaluate(...)` runs as native Playwright.
*
* #### Configuration
*
* `inject` maps an injection name to a `HelperName.propertyName` string. A
* value with no dot is shorthand for "first configured browser helper that
* exposes this property" (allowed properties: `page`, `browser`,
* `browserContext`, `context`).
*
* ```js
* plugins: {
* expose: {
* enabled: true,
* inject: {
* page: 'Playwright.page',
* browser: 'Playwright.browser',
* browserContext: 'Playwright.browserContext',
* frame: 'Playwright.context', // current frame set by switchTo
* wdio: 'WebDriver.browser',
* }
* }
* }
* ```
*
* Shorthand:
*
* ```js
* plugins: {
* expose: {
* enabled: true,
* inject: {
* page: 'page', // resolves to Playwright.page or Puppeteer.page
* }
* }
* }
* ```
*
* #### Caveats
*
* - The injected value is a `Proxy`, not the actual `Page`/`Browser` instance,
* so `page instanceof Page` is `false`. Use duck typing instead.
* - Cached method references lose the live binding. Call `page.click(...)`,
* not `const click = page.click; click(...)`.
* - In dry-run mode the underlying helper property is `undefined`; accessing
* any property on the proxy returns `undefined` rather than throwing.
*/
export default function (config = {}) {
config = { ...defaultConfig, ...config }
const mappings = parseMappings(config.inject)
const support = {}
for (const [name, { helperName, property }] of Object.entries(mappings)) {
support[name] = makeLiveProxy(helperName, property)
}
Container.append({ support })
}
function parseMappings(inject) {
const out = {}
for (const [name, value] of Object.entries(inject || {})) {
if (RESERVED_NAMES.has(name)) {
throw new Error(`expose plugin: inject name '${name}' is reserved`)
}
if (typeof value !== 'string' || !value) {
throw new Error(`expose plugin: inject value for '${name}' must be a non-empty string`)
}
let helperName
let property
if (value.includes('.')) {
const dot = value.indexOf('.')
helperName = value.slice(0, dot)
property = value.slice(dot + 1)
if (!helperName || !property) {
throw new Error(`expose plugin: invalid inject value '${value}' for '${name}' (expected 'HelperName.propertyName')`)
}
if (!Container.helpers(helperName)) {
throw new Error(`expose plugin: helper '${helperName}' is not configured (needed for inject '${name}')`)
}
} else {
property = value
if (!SHORTHAND_PROPERTIES.has(property)) {
throw new Error(`expose plugin: shorthand '${property}' is not a known helper property for '${name}' (use 'HelperName.${property}' instead)`)
}
helperName = Container.STANDARD_ACTING_HELPERS.find(h => Container.helpers(h))
if (!helperName) {
throw new Error(`expose plugin: no standard browser helper configured (needed for inject '${name}')`)
}
}
out[name] = { helperName, property }
}
return out
}
function makeLiveProxy(helperName, property) {
const resolve = () => Container.helpers(helperName)?.[property]
return new Proxy(function () {}, {
get(_, prop) {
const target = resolve()
if (target == null) return undefined
const value = target[prop]
if (typeof value === 'function') return value.bind(target)
return value
},
has(_, prop) {
const target = resolve()
return target != null && prop in target
},
apply(_, thisArg, args) {
const target = resolve()
return target?.apply(thisArg, args)
},
set(_, prop, value) {
const target = resolve()
if (target != null) target[prop] = value
return true
},
getPrototypeOf() {
const target = resolve()
return target != null ? Object.getPrototypeOf(target) : null
},
ownKeys() {
const target = resolve()
return target != null ? Reflect.ownKeys(target) : []
},
getOwnPropertyDescriptor(_, prop) {
const target = resolve()
return target != null ? Object.getOwnPropertyDescriptor(target, prop) : undefined
},
})
}