@applitools/driver 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/context.ts DELETED
@@ -1,519 +0,0 @@
1
- import type * as types from '@applitools/types'
2
- import type {Driver} from './driver'
3
- import type {SpecUtils} from './utils'
4
- import * as utils from '@applitools/utils'
5
- import {makeSpecUtils} from './utils'
6
- import {Element} from './element'
7
-
8
- const snippets = require('@applitools/snippets')
9
-
10
- export type ContextReference<TDriver, TContext, TElement, TSelector> =
11
- | Context<TDriver, TContext, TElement, TSelector>
12
- | Element<TDriver, TContext, TElement, TSelector>
13
- | TElement
14
- | types.Selector<TSelector>
15
- | string
16
- | number
17
-
18
- export type ContextPlain<TDriver, TContext, TElement, TSelector> =
19
- | ContextReference<TDriver, TContext, TElement, TSelector>
20
- | {
21
- reference: ContextReference<TDriver, TContext, TElement, TSelector>
22
- scrollingElement?: Element<TDriver, TContext, TElement, TSelector>
23
- parent?: ContextPlain<TDriver, TContext, TElement, TSelector>
24
- }
25
-
26
- export type ContextState = {
27
- region?: types.Region
28
- clientRegion?: types.Region
29
- scrollingRegion?: types.Region
30
- innerOffset?: types.Location
31
- }
32
-
33
- export class Context<TDriver, TContext, TElement, TSelector> {
34
- private _target: TContext
35
-
36
- private _driver: Driver<TDriver, TContext, TElement, TSelector>
37
- private _parent: Context<TDriver, TContext, TElement, TSelector>
38
- private _element: Element<TDriver, TContext, TElement, TSelector>
39
- private _reference: ContextReference<TDriver, TContext, TElement, TSelector>
40
- private _scrollingElement: Element<TDriver, TContext, TElement, TSelector>
41
- private _state: ContextState = {}
42
- private _logger: any
43
- private _utils: SpecUtils<TDriver, TContext, TElement, TSelector>
44
-
45
- private _isReference(reference: any): reference is ContextReference<TDriver, TContext, TElement, TSelector> {
46
- return (
47
- reference instanceof Context ||
48
- utils.types.isInteger(reference) ||
49
- utils.types.isString(reference) ||
50
- reference instanceof Element ||
51
- this._spec.isElement(reference) ||
52
- this._utils.isSelector(reference)
53
- )
54
- }
55
-
56
- protected readonly _spec: types.SpecDriver<TDriver, TContext, TElement, TSelector>
57
-
58
- constructor(options: {
59
- spec: types.SpecDriver<TDriver, TContext, TElement, TSelector>
60
- context?: TContext | Context<TDriver, TContext, TElement, TSelector>
61
- driver?: Driver<TDriver, TContext, TElement, TSelector>
62
- parent?: Context<TDriver, TContext, TElement, TSelector>
63
- reference?: ContextReference<TDriver, TContext, TElement, TSelector>
64
- element?: Element<TDriver, TContext, TElement, TSelector>
65
- scrollingElement?: Element<TDriver, TContext, TElement, TSelector>
66
- logger?: any
67
- }) {
68
- if (options.context instanceof Context) return options.context
69
-
70
- this._spec = options.spec
71
- this._utils = makeSpecUtils(options.spec)
72
-
73
- if (options.logger) this._logger = options.logger
74
-
75
- if (options.context) {
76
- if (this._spec.isContext?.(options.context) ?? this._spec.isDriver(options.context)) {
77
- this._target = options.context
78
- } else {
79
- throw new TypeError('Context constructor called with argument of unknown type of context!')
80
- }
81
- }
82
-
83
- if (this._isReference(options.reference)) {
84
- if (options.reference instanceof Context) return options.reference
85
- if (!options.parent) {
86
- throw new TypeError('Cannot construct child context without reference to the parent')
87
- }
88
-
89
- this._reference = options.reference
90
- this._parent = options.parent
91
- this._scrollingElement = options.scrollingElement
92
- this._driver = options.driver || this._parent.driver
93
- } else if (!options.reference) {
94
- this._element = null
95
- this._parent = null
96
- this._scrollingElement = options.scrollingElement
97
- this._driver = options.driver
98
- } else {
99
- throw new TypeError('Context constructor called with argument of unknown type!')
100
- }
101
- }
102
-
103
- get target(): TContext {
104
- return this._target
105
- }
106
-
107
- get driver(): Driver<TDriver, TContext, TElement, TSelector> {
108
- return this._driver
109
- }
110
-
111
- get parent(): Context<TDriver, TContext, TElement, TSelector> | null {
112
- return this._parent ?? null
113
- }
114
-
115
- get main(): Context<TDriver, TContext, TElement, TSelector> {
116
- return this.parent?.main ?? this
117
- }
118
-
119
- get path(): Context<TDriver, TContext, TElement, TSelector>[] {
120
- return [...(this.parent?.path ?? []), this]
121
- }
122
-
123
- get isMain(): boolean {
124
- return this.main === this
125
- }
126
-
127
- get isCurrent(): boolean {
128
- return this.driver.currentContext === this
129
- }
130
-
131
- get isInitialized(): boolean {
132
- return Boolean(this._element) || this.isMain
133
- }
134
-
135
- get isRef(): boolean {
136
- return !this._target
137
- }
138
-
139
- async init(): Promise<this> {
140
- if (this.isInitialized) return this
141
- if (!this._reference) throw new TypeError('Cannot initialize context without a reference to the context element')
142
-
143
- await this.parent.focus()
144
-
145
- this._logger.log('Context initialization')
146
-
147
- if (utils.types.isInteger(this._reference)) {
148
- this._logger.log('Getting context element using index:', this._reference)
149
- const elements = await this.parent.elements('frame, iframe')
150
- if (this._reference > elements.length) {
151
- throw new TypeError(`Context element with index ${this._reference} is not found`)
152
- }
153
- this._element = elements[this._reference]
154
- } else if (utils.types.isString(this._reference) || this._utils.isSelector(this._reference)) {
155
- if (utils.types.isString(this._reference)) {
156
- this._logger.log('Getting context element by name or id', this._reference)
157
- this._element = await this.parent
158
- .element(`iframe[name="${this._reference}"], iframe#${this._reference}`)
159
- .catch(() => null)
160
- }
161
- if (!this._element && this._utils.isSelector(this._reference)) {
162
- this._logger.log('Getting context element by selector', this._reference)
163
- this._element = await this.parent.element(this._reference)
164
- }
165
- if (!this._element) {
166
- throw new TypeError(
167
- `Context element with name, id, or selector ${JSON.stringify(this._reference)}' is not found`,
168
- )
169
- }
170
- } else if (this._spec.isElement(this._reference) || this._reference instanceof Element) {
171
- this._logger.log('Initialize context from reference element', this._reference)
172
- this._element = new Element({
173
- spec: this._spec,
174
- context: this.parent,
175
- element: this._reference,
176
- logger: this._logger,
177
- })
178
- } else {
179
- throw new TypeError('Reference type does not supported')
180
- }
181
-
182
- this._reference = null
183
-
184
- return this
185
- }
186
-
187
- async focus(): Promise<this> {
188
- if (this.isCurrent) {
189
- return this
190
- } else if (this.isMain) {
191
- await this.driver.switchToMainContext()
192
- return this
193
- }
194
-
195
- if (this.isRef) {
196
- await this.init()
197
- }
198
-
199
- if (!this.parent.isCurrent) {
200
- await this.driver.switchTo(this)
201
- return this
202
- }
203
-
204
- await this.parent.preserveInnerOffset()
205
-
206
- if (this.parent.isMain) await this.parent.preserveContextRegions()
207
- await this.preserveContextRegions()
208
-
209
- this._target = await this._spec.childContext(this.parent.target, this._element.target)
210
-
211
- this.driver.updateCurrentContext(this)
212
-
213
- return this
214
- }
215
-
216
- async equals(
217
- context: Context<TDriver, TContext, TElement, TSelector> | Element<TDriver, TContext, TElement, TSelector>,
218
- ): Promise<boolean> {
219
- if (context === this || (this.isMain && context === null)) return true
220
- if (!this._element) return false
221
- return this._element.equals(context instanceof Context ? await context.getContextElement() : context)
222
- }
223
-
224
- async context(
225
- reference: ContextPlain<TDriver, TContext, TElement, TSelector>,
226
- ): Promise<Context<TDriver, TContext, TElement, TSelector>> {
227
- if (reference instanceof Context) {
228
- if (reference.parent !== this && !(await this.equals(reference.parent))) {
229
- throw Error('Cannot attach a child context because it has a different parent')
230
- }
231
- return reference
232
- } else if (this._isReference(reference)) {
233
- return new Context({spec: this._spec, parent: this, driver: this.driver, reference, logger: this._logger})
234
- } else if (utils.types.has(reference, 'reference')) {
235
- const parent = reference.parent ? await this.context(reference.parent) : this
236
- return new Context({
237
- spec: this._spec,
238
- parent,
239
- driver: this.driver,
240
- reference: reference.reference,
241
- scrollingElement: reference?.scrollingElement,
242
- logger: this._logger,
243
- })
244
- }
245
- }
246
-
247
- async element(
248
- elementOrSelector: TElement | types.Selector<TSelector>,
249
- ): Promise<Element<TDriver, TContext, TElement, TSelector>> {
250
- if (this._spec.isElement(elementOrSelector)) {
251
- return new Element({spec: this._spec, context: this, element: elementOrSelector, logger: this._logger})
252
- } else if (this._utils.isSelector(elementOrSelector)) {
253
- if (this.isRef) {
254
- return new Element({spec: this._spec, context: this, selector: elementOrSelector, logger: this._logger})
255
- }
256
- await this.focus()
257
-
258
- const selectors = this._utils.splitSelector(elementOrSelector)
259
- const contextSelectors = selectors.slice(0, -1)
260
- const elementSelector = selectors[selectors.length - 1]
261
-
262
- let context = this as Context<TDriver, TContext, TElement, TSelector>
263
- for (const contextSelector of contextSelectors) {
264
- context = await context
265
- .context(contextSelector)
266
- .then(context => context.focus())
267
- .catch(() => null)
268
- if (!context) return null
269
- }
270
-
271
- const {root, selector} = this.driver.features?.shadowSelector
272
- ? {root: null, selector: elementSelector}
273
- : await this._utils.findRootElement(context.target, elementSelector)
274
-
275
- if (!root && selector !== elementSelector) return null
276
-
277
- const element = await this._spec.findElement(context.target, this._utils.transformSelector(selector), root)
278
- return element
279
- ? new Element({spec: this._spec, context, element, selector: elementSelector, logger: this._logger})
280
- : null
281
- } else {
282
- throw new TypeError('Cannot find element using argument of unknown type!')
283
- }
284
- }
285
-
286
- async elements(
287
- elementOrSelector: TElement | types.Selector<TSelector>,
288
- ): Promise<Element<TDriver, TContext, TElement, TSelector>[]> {
289
- if (this._spec.isElement(elementOrSelector)) {
290
- return [new Element({spec: this._spec, context: this, element: elementOrSelector, logger: this._logger})]
291
- } else if (this._utils.isSelector(elementOrSelector)) {
292
- if (this.isRef) {
293
- return [new Element({spec: this._spec, context: this, selector: elementOrSelector, logger: this._logger})]
294
- }
295
- await this.focus()
296
-
297
- const selectors = this._utils.splitSelector(elementOrSelector)
298
- const contextSelectors = selectors.slice(0, -1)
299
- const elementSelector = selectors[selectors.length - 1]
300
-
301
- let context = this as Context<TDriver, TContext, TElement, TSelector>
302
- for (const contextSelector of contextSelectors) {
303
- context = await context
304
- .context(contextSelector)
305
- .then(context => context.focus())
306
- .catch(() => null)
307
- if (!context) return []
308
- }
309
-
310
- const {root, selector} = this.driver.features?.shadowSelector
311
- ? {root: null, selector: elementSelector}
312
- : await this._utils.findRootElement(context.target, elementSelector)
313
-
314
- if (!root && selector !== elementSelector) return []
315
-
316
- const elements = await this._spec.findElements(context.target, this._utils.transformSelector(selector), root)
317
- return elements.map((element, index) => {
318
- return new Element({
319
- spec: this._spec,
320
- context,
321
- element,
322
- selector: elementSelector,
323
- index,
324
- logger: this._logger,
325
- })
326
- })
327
- } else {
328
- throw new TypeError('Cannot find elements using argument of unknown type!')
329
- }
330
- }
331
-
332
- async execute(script: ((args: any) => any) | string, arg?: any): Promise<any> {
333
- await this.focus()
334
- try {
335
- return await this._spec.executeScript(this.target, script, serialize.call(this, arg))
336
- } catch (err) {
337
- this._logger.warn('Error during script execution with argument', arg)
338
- this._logger.error(err)
339
- throw err
340
- }
341
-
342
- function serialize(this: Context<TDriver, TContext, TElement, TSelector>, value: any): any {
343
- if (this._spec.isElement(value) || value instanceof Element) {
344
- return value instanceof Element ? value.toJSON() : value
345
- } else if (utils.types.isArray(value)) {
346
- return value.map(value => serialize.call(this, value))
347
- } else if (utils.types.isObject(value)) {
348
- return Object.entries(value.toJSON?.() ?? value).reduce((serialized, [key, value]) => {
349
- return Object.assign(serialized, {[key]: serialize.call(this, value)})
350
- }, {})
351
- } else {
352
- return value
353
- }
354
- }
355
- }
356
-
357
- async getContextElement(): Promise<Element<TDriver, TContext, TElement, TSelector>> {
358
- if (this.isMain) return null
359
- await this.init()
360
- return this._element
361
- }
362
-
363
- async getScrollingElement(): Promise<Element<TDriver, TContext, TElement, TSelector>> {
364
- if (!(this._scrollingElement instanceof Element)) {
365
- await this.focus()
366
- if (this._scrollingElement) {
367
- this._scrollingElement = await this.element(this._scrollingElement)
368
- } else {
369
- this._scrollingElement = await this.element(
370
- this.driver.isWeb ? {type: 'css', selector: 'html'} : {type: 'xpath', selector: '//*[@scrollable="true"]'},
371
- )
372
- }
373
- }
374
- return this._scrollingElement
375
- }
376
-
377
- async setScrollingElement(
378
- scrollingElement: Element<TDriver, TContext, TElement, TSelector> | TElement | types.Selector<TSelector>,
379
- ): Promise<void> {
380
- if (scrollingElement === undefined) return
381
- else if (scrollingElement === null) this._scrollingElement = null
382
- else {
383
- this._scrollingElement =
384
- scrollingElement instanceof Element ? scrollingElement : await this.element(scrollingElement)
385
- }
386
- }
387
-
388
- async blurElement(element?: Element<TDriver, TContext, TElement, TSelector>): Promise<TElement> {
389
- try {
390
- return await this.execute(snippets.blurElement, [element])
391
- } catch (err) {
392
- this._logger.warn('Cannot blur element', element)
393
- this._logger.error(err)
394
- return null
395
- }
396
- }
397
-
398
- async focusElement(element: Element<TDriver, TContext, TElement, TSelector>) {
399
- try {
400
- return await this.execute(snippets.focusElement, [element])
401
- } catch (err) {
402
- this._logger.warn('Cannot focus element', element)
403
- this._logger.error(err)
404
- return null
405
- }
406
- }
407
-
408
- async getRegion(): Promise<types.Region> {
409
- if (this.isMain && this.isCurrent) {
410
- const viewportRegion = utils.geometry.region({x: 0, y: 0}, await this.driver.getViewportSize())
411
- this._state.region = this._scrollingElement
412
- ? utils.geometry.region(
413
- {x: 0, y: 0},
414
- utils.geometry.intersect(viewportRegion, await this._scrollingElement.getRegion()),
415
- )
416
- : viewportRegion
417
- } else if (this.parent?.isCurrent) {
418
- await this.init()
419
- this._state.region = await this._element.getRegion()
420
- }
421
- return this._state.region
422
- }
423
-
424
- async getClientRegion(): Promise<types.Region> {
425
- if (this.isMain && this.isCurrent) {
426
- const viewportRegion = utils.geometry.region({x: 0, y: 0}, await this.driver.getViewportSize())
427
- this._state.clientRegion = this._scrollingElement
428
- ? utils.geometry.region(
429
- {x: 0, y: 0},
430
- utils.geometry.intersect(viewportRegion, await this._scrollingElement.getClientRegion()),
431
- )
432
- : viewportRegion
433
- } else if (this.parent?.isCurrent) {
434
- await this.init()
435
- this._state.clientRegion = await this._element.getClientRegion()
436
- }
437
- return this._state.clientRegion
438
- }
439
-
440
- async getScrollingRegion(): Promise<types.Region> {
441
- if (this.isCurrent) {
442
- const scrollingElement = await this.getScrollingElement()
443
- this._state.scrollingRegion = await scrollingElement.getClientRegion()
444
- }
445
- return this._state.scrollingRegion
446
- }
447
-
448
- async getContentSize(): Promise<types.Size> {
449
- return this.execute(snippets.getDocumentSize)
450
- }
451
-
452
- async getInnerOffset(): Promise<types.Location> {
453
- if (this.isCurrent) {
454
- const scrollingElement = await this.getScrollingElement()
455
- this._state.innerOffset = scrollingElement ? await scrollingElement.getInnerOffset() : {x: 0, y: 0}
456
- }
457
- return this._state.innerOffset
458
- }
459
-
460
- async getLocationInMainContext(): Promise<types.Location> {
461
- return this.path.reduce((location, context) => {
462
- return location.then(async location => {
463
- return utils.geometry.offset(location, utils.geometry.location(await context.getClientRegion()))
464
- })
465
- }, Promise.resolve({x: 0, y: 0}))
466
- }
467
-
468
- async getLocationInViewport(): Promise<types.Location> {
469
- let location = utils.geometry.offsetNegative({x: 0, y: 0}, await this.getInnerOffset())
470
-
471
- if (this.isMain) return location
472
-
473
- let currentContext = this as Context<TDriver, TContext, TElement, TSelector>
474
- while (currentContext) {
475
- const contextLocation = utils.geometry.location(await currentContext.getClientRegion())
476
- const parentContextInnerOffset = (await currentContext.parent?.getInnerOffset()) ?? {x: 0, y: 0}
477
-
478
- location = utils.geometry.offsetNegative(
479
- utils.geometry.offset(location, contextLocation),
480
- parentContextInnerOffset,
481
- )
482
- currentContext = currentContext.parent
483
- }
484
- return location
485
- }
486
-
487
- async getRegionInViewport(region: types.Region): Promise<types.Region> {
488
- let currentContext = this as Context<TDriver, TContext, TElement, TSelector>
489
-
490
- if (region) region = utils.geometry.offsetNegative(region, await currentContext.getInnerOffset())
491
- else region = {x: 0, y: 0, width: Infinity, height: Infinity}
492
-
493
- while (currentContext) {
494
- const contextRegion = await currentContext.getClientRegion()
495
- // const contextScrollingRegion = await currentContext.getScrollingRegion()
496
- const parentContextInnerOffset = (await currentContext.parent?.getInnerOffset()) ?? {x: 0, y: 0}
497
-
498
- region = utils.geometry.intersect(contextRegion, utils.geometry.offset(region, contextRegion))
499
- // region = utils.geometry.intersect(contextScrollingRegion, region)
500
- region = utils.geometry.offsetNegative(region, parentContextInnerOffset)
501
-
502
- currentContext = currentContext.parent
503
- }
504
- return region
505
- }
506
-
507
- private async preserveInnerOffset() {
508
- this._state.innerOffset = await this.getInnerOffset()
509
- }
510
-
511
- private async preserveContextRegions() {
512
- this._state.region = await this.getRegion()
513
- this._state.clientRegion = await this.getClientRegion()
514
- }
515
-
516
- private async preserveScrollingRegion() {
517
- this._state.scrollingRegion = await this.getScrollingRegion()
518
- }
519
- }