@applitools/core 4.69.1 → 4.70.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/CHANGELOG.md +53 -0
- package/dist/classic/utils/parse-ios-page-source.js +76 -0
- package/dist/classic/utils/synthesize-native-dom.js +196 -0
- package/dist/classic/utils/take-ios-dom-capture.js +28 -0
- package/dist/classic/utils/take-screenshots.js +23 -1
- package/dist/open-eyes.js +24 -20
- package/package.json +18 -15
- package/types/automation/utils/take-screenshot.d.ts +2 -0
- package/types/classic/utils/parse-ios-page-source.d.ts +2 -0
- package/types/classic/utils/synthesize-native-dom.d.ts +44 -0
- package/types/classic/utils/take-ios-dom-capture.d.ts +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [4.70.0](https://github.com/Applitools-Dev/sdk/compare/js/core@4.69.2...js/core@4.70.0) (2026-09-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* synthesize a DOM from the accessibility hierarchy for iOS checks without NML | AD-14691 ([#4112](https://github.com/Applitools-Dev/sdk/issues/4112)) ([ee6e04e](https://github.com/Applitools-Dev/sdk/commit/ee6e04e7b46ce8d530202b9bd40cdce255a74728))
|
|
9
|
+
|
|
10
|
+
## [4.69.2](https://github.com/Applitools-Dev/sdk/compare/js/core@4.69.1...js/core@4.69.2) (2026-08-30)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* don't send the bare branch name under an scm integration | AD-15642 ([#4140](https://github.com/Applitools-Dev/sdk/issues/4140)) ([41c30b9](https://github.com/Applitools-Dev/sdk/commit/41c30b9999488ebc635528476888a5fc239654a8))
|
|
16
|
+
* resolve native iOS viewport in screen space | FLD-4552 ([#4088](https://github.com/Applitools-Dev/sdk/issues/4088)) ([e2f9b03](https://github.com/Applitools-Dev/sdk/commit/e2f9b030103c066faac45d69f98cee265a0a931a))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Dependencies
|
|
20
|
+
|
|
21
|
+
* @applitools/dom-snapshot bumped to 4.18.2
|
|
22
|
+
#### Performance Improvements
|
|
23
|
+
|
|
24
|
+
* emit adopted stylesheets as placeholder nodes | AD-15243 ([#4118](https://github.com/Applitools-Dev/sdk/issues/4118)) ([7486bbb](https://github.com/Applitools-Dev/sdk/commit/7486bbbc6f599d1de499e031a59eb4502de8b6b1))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
* @applitools/driver bumped to 1.26.10
|
|
29
|
+
#### Bug Fixes
|
|
30
|
+
|
|
31
|
+
* resolve native iOS viewport in screen space | FLD-4552 ([#4088](https://github.com/Applitools-Dev/sdk/issues/4088)) ([e2f9b03](https://github.com/Applitools-Dev/sdk/commit/e2f9b030103c066faac45d69f98cee265a0a931a))
|
|
32
|
+
* @applitools/screenshoter bumped to 3.12.31
|
|
33
|
+
#### Bug Fixes
|
|
34
|
+
|
|
35
|
+
* resolve native iOS viewport in screen space | FLD-4552 ([#4088](https://github.com/Applitools-Dev/sdk/issues/4088)) ([e2f9b03](https://github.com/Applitools-Dev/sdk/commit/e2f9b030103c066faac45d69f98cee265a0a931a))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
* @applitools/ufg-client bumped to 1.23.3
|
|
40
|
+
#### Bug Fixes
|
|
41
|
+
|
|
42
|
+
* extract css dependencies in chunks past the parser's offset limit | FLD-4643 ([#4134](https://github.com/Applitools-Dev/sdk/issues/4134)) ([1ffdc7b](https://github.com/Applitools-Dev/sdk/commit/1ffdc7bc7e1384bfc18ae9a1d5afbf7c342f8f40))
|
|
43
|
+
* @applitools/spec-driver-webdriver bumped to 1.6.10
|
|
44
|
+
|
|
45
|
+
* @applitools/spec-driver-selenium bumped to 1.8.10
|
|
46
|
+
|
|
47
|
+
* @applitools/spec-driver-playwright bumped to 1.9.10
|
|
48
|
+
|
|
49
|
+
* @applitools/spec-driver-puppeteer bumped to 1.8.10
|
|
50
|
+
|
|
51
|
+
* @applitools/nml-client bumped to 1.12.2
|
|
52
|
+
|
|
53
|
+
* @applitools/ec-client bumped to 1.12.45
|
|
54
|
+
|
|
55
|
+
|
|
3
56
|
## [4.69.1](https://github.com/Applitools-Dev/sdk/compare/js/core@4.69.0...js/core@4.69.1) (2026-08-23)
|
|
4
57
|
|
|
5
58
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseIosPageSource = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Parses an iOS Appium `getPageSource` document (WebDriverAgent XML) into the
|
|
6
|
+
* `NativeElementNode` tree consumed by `synthesize-native-dom`. Kept separate
|
|
7
|
+
* from the synthesizer so the element-type -> HTML mapping stays platform-neutral
|
|
8
|
+
* and the (platform-specific) source shape lives here.
|
|
9
|
+
*
|
|
10
|
+
* WDA emits every element as `<XCUIElementType… type="XCUIElementType…"
|
|
11
|
+
* label="…" x=".." y=".." width=".." height=".." …>`; `preserveOrder` keeps the
|
|
12
|
+
* original sibling order, which the dashboard hover/layout relies on.
|
|
13
|
+
*/
|
|
14
|
+
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
15
|
+
const ATTRS = ':@';
|
|
16
|
+
const ATTR_PREFIX = '@_';
|
|
17
|
+
const parser = new fast_xml_parser_1.XMLParser({
|
|
18
|
+
ignoreAttributes: false,
|
|
19
|
+
preserveOrder: true,
|
|
20
|
+
parseAttributeValue: false,
|
|
21
|
+
// Decode decimal char refs too (` ` for newlines in multi-line labels) — the
|
|
22
|
+
// default only handles named/hex. Keep label whitespace intact (`trimValues:false`)
|
|
23
|
+
// so labels match the on-device value; inter-element whitespace becomes stray #text
|
|
24
|
+
// nodes that `toNativeNode` drops (they aren't XCUIElementType tags).
|
|
25
|
+
htmlEntities: true,
|
|
26
|
+
trimValues: false,
|
|
27
|
+
});
|
|
28
|
+
function parseIosPageSource(xml) {
|
|
29
|
+
var _a;
|
|
30
|
+
// WDA truncates the page source on deep hierarchies. `parser.parse` accepts a
|
|
31
|
+
// truncated document (unclosed tags) and returns a partial tree instead of
|
|
32
|
+
// throwing, so validate first — otherwise we'd upload a partial DOM as complete.
|
|
33
|
+
if (fast_xml_parser_1.XMLValidator.validate(xml) !== true)
|
|
34
|
+
return undefined;
|
|
35
|
+
const top = parser.parse(xml);
|
|
36
|
+
const aut = top.find(node => Array.isArray(node.AppiumAUT));
|
|
37
|
+
const children = (_a = aut === null || aut === void 0 ? void 0 : aut.AppiumAUT) !== null && _a !== void 0 ? _a : top;
|
|
38
|
+
// The root is the first real XCUIElement under <AppiumAUT> (the application).
|
|
39
|
+
const rootNode = children.find(node => elementTag(node));
|
|
40
|
+
return rootNode && toNativeNode(rootNode);
|
|
41
|
+
}
|
|
42
|
+
exports.parseIosPageSource = parseIosPageSource;
|
|
43
|
+
function toNativeNode(node) {
|
|
44
|
+
var _a, _b, _c;
|
|
45
|
+
const tag = elementTag(node);
|
|
46
|
+
if (!tag)
|
|
47
|
+
return undefined;
|
|
48
|
+
const attrs = (_a = node[ATTRS]) !== null && _a !== void 0 ? _a : {};
|
|
49
|
+
const type = (_b = attrs[`${ATTR_PREFIX}type`]) !== null && _b !== void 0 ? _b : tag;
|
|
50
|
+
const label = attrs[`${ATTR_PREFIX}label`];
|
|
51
|
+
const children = (_c = node[tag]) !== null && _c !== void 0 ? _c : [];
|
|
52
|
+
return {
|
|
53
|
+
elementType: caseName(type),
|
|
54
|
+
frame: toFrame(attrs),
|
|
55
|
+
label: label != null && label !== '' ? String(label) : undefined,
|
|
56
|
+
children: children.map(toNativeNode).filter(Boolean),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** The single `XCUIElementType…` key on a parsed node (ignoring the `:@` attrs bag). */
|
|
60
|
+
function elementTag(node) {
|
|
61
|
+
return Object.keys(node).find(key => key.startsWith('XCUIElementType'));
|
|
62
|
+
}
|
|
63
|
+
/** 'XCUIElementTypeStaticText' -> 'staticText' — the case-name form HTMLNode switches on. */
|
|
64
|
+
function caseName(type) {
|
|
65
|
+
const name = type.replace(/^XCUIElementType/, '');
|
|
66
|
+
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
67
|
+
}
|
|
68
|
+
function toFrame(attrs) {
|
|
69
|
+
const x = Number(attrs[`${ATTR_PREFIX}x`]);
|
|
70
|
+
const y = Number(attrs[`${ATTR_PREFIX}y`]);
|
|
71
|
+
const width = Number(attrs[`${ATTR_PREFIX}width`]);
|
|
72
|
+
const height = Number(attrs[`${ATTR_PREFIX}height`]);
|
|
73
|
+
if ([x, y, width, height].some(Number.isNaN))
|
|
74
|
+
return undefined;
|
|
75
|
+
return { x, y, width, height };
|
|
76
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Synthesizes an Applitools DOM-capture document from a native accessibility
|
|
4
|
+
* tree — a port of the iOS SDK's `HTMLNode.swift` mapping that must stay in sync
|
|
5
|
+
* with it. See core/llm-docs/CLASSIC.md ("iOS DOM Synthesis").
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.synthesizeNativeDom = void 0;
|
|
9
|
+
/** elementType -> tag + static attributes (mirrors the switch in HTMLNode.swift:205-434). */
|
|
10
|
+
const TYPE_MAP = {
|
|
11
|
+
any: { tag: 'DIV' },
|
|
12
|
+
other: { tag: 'DIV' },
|
|
13
|
+
application: { tag: 'DIV' },
|
|
14
|
+
group: { tag: 'DIV', attrs: { role: 'group' } },
|
|
15
|
+
window: { tag: 'DIV', attrs: { role: 'window' } },
|
|
16
|
+
sheet: { tag: 'DIV', attrs: { role: 'dialog' } },
|
|
17
|
+
drawer: { tag: 'DIV', attrs: { role: 'complementary' } },
|
|
18
|
+
alert: { tag: 'DIV', attrs: { role: 'alert' } },
|
|
19
|
+
dialog: { tag: 'DIV', attrs: { role: 'dialog' } },
|
|
20
|
+
button: { tag: 'BUTTON' },
|
|
21
|
+
radioButton: { tag: 'INPUT', attrs: { type: 'radio' } },
|
|
22
|
+
radioGroup: { tag: 'DIV', attrs: { role: 'radiogroup' } },
|
|
23
|
+
checkBox: { tag: 'INPUT', attrs: { type: 'checkbox' } },
|
|
24
|
+
disclosureTriangle: { tag: 'BUTTON', attrs: { 'aria-expanded': 'false' } },
|
|
25
|
+
popUpButton: { tag: 'DIV', attrs: { role: 'listbox' } },
|
|
26
|
+
comboBox: { tag: 'INPUT', attrs: { role: 'combobox' } },
|
|
27
|
+
menuButton: { tag: 'BUTTON', attrs: { 'aria-haspopup': 'true' } },
|
|
28
|
+
toolbarButton: { tag: 'BUTTON' },
|
|
29
|
+
popover: { tag: 'DIV', attrs: { role: 'dialog' } },
|
|
30
|
+
keyboard: { tag: 'DIV', attrs: { class: 'keyboard' } },
|
|
31
|
+
key: { tag: 'KBD', attrs: { class: 'key' } },
|
|
32
|
+
navigationBar: { tag: 'NAV', attrs: { role: 'navigation' } },
|
|
33
|
+
tabBar: { tag: 'DIV', attrs: { role: 'tablist' } },
|
|
34
|
+
tabGroup: { tag: 'DIV', attrs: { role: 'tablist' } },
|
|
35
|
+
toolbar: { tag: 'DIV', attrs: { role: 'toolbar' } },
|
|
36
|
+
statusBar: { tag: 'DIV', attrs: { role: 'status' } },
|
|
37
|
+
table: { tag: 'UL' },
|
|
38
|
+
collectionView: { tag: 'UL' },
|
|
39
|
+
tableRow: { tag: 'DIV', attrs: { role: 'row' } },
|
|
40
|
+
tableColumn: { tag: 'DIV', attrs: { role: 'column' } },
|
|
41
|
+
outline: { tag: 'UL', attrs: { role: 'tree' } },
|
|
42
|
+
outlineRow: { tag: 'LI', attrs: { role: 'treeitem' } },
|
|
43
|
+
browser: { tag: 'CANVAS' },
|
|
44
|
+
map: { tag: 'CANVAS' },
|
|
45
|
+
webView: { tag: 'CANVAS' },
|
|
46
|
+
slider: { tag: 'INPUT', attrs: { type: 'range' } },
|
|
47
|
+
pageIndicator: { tag: 'DIV', attrs: { class: 'page-indicator' } },
|
|
48
|
+
progressIndicator: { tag: 'DIV', attrs: { role: 'progressbar' } },
|
|
49
|
+
activityIndicator: { tag: 'DIV', attrs: { role: 'status' } },
|
|
50
|
+
segmentedControl: { tag: 'DIV', attrs: { role: 'group' } },
|
|
51
|
+
picker: { tag: 'DIV', attrs: { role: 'listbox' } },
|
|
52
|
+
pickerWheel: { tag: 'DIV', attrs: { role: 'listbox' } },
|
|
53
|
+
switch: { tag: 'INPUT', attrs: { type: 'checkbox', role: 'switch' } },
|
|
54
|
+
toggle: { tag: 'BUTTON', attrs: { role: 'switch', 'aria-pressed': 'false' } },
|
|
55
|
+
link: { tag: 'A', attrs: { role: 'link' } },
|
|
56
|
+
image: { tag: 'IMG' },
|
|
57
|
+
icon: { tag: 'DIV', attrs: { class: 'icon' } },
|
|
58
|
+
searchField: { tag: 'INPUT', attrs: { type: 'search' } },
|
|
59
|
+
scrollView: { tag: 'DIV', attrs: { style: 'overflow:auto;' } },
|
|
60
|
+
scrollBar: { tag: 'DIV', attrs: { class: 'scrollbar' } },
|
|
61
|
+
staticText: { tag: 'SPAN' },
|
|
62
|
+
// textField / secureTextField also set value=label||'' (handled below).
|
|
63
|
+
textField: { tag: 'INPUT', attrs: { type: 'text' } },
|
|
64
|
+
secureTextField: { tag: 'INPUT', attrs: { type: 'password' } },
|
|
65
|
+
datePicker: { tag: 'INPUT', attrs: { type: 'date' } },
|
|
66
|
+
textView: { tag: 'TEXTAREA' },
|
|
67
|
+
menu: { tag: 'UL', attrs: { role: 'menu' } },
|
|
68
|
+
menuItem: { tag: 'LI', attrs: { role: 'menuitem' } },
|
|
69
|
+
menuBar: { tag: 'DIV', attrs: { role: 'menubar' } },
|
|
70
|
+
menuBarItem: { tag: 'DIV', attrs: { role: 'menuitem' } },
|
|
71
|
+
incrementArrow: { tag: 'BUTTON', attrs: { 'aria-label': 'increment' } },
|
|
72
|
+
decrementArrow: { tag: 'BUTTON', attrs: { 'aria-label': 'decrement' } },
|
|
73
|
+
timeline: { tag: 'DIV', attrs: { class: 'timeline' } },
|
|
74
|
+
ratingIndicator: { tag: 'DIV', attrs: { role: 'meter' } },
|
|
75
|
+
valueIndicator: { tag: 'DIV', attrs: { role: 'output' } },
|
|
76
|
+
splitGroup: { tag: 'DIV', attrs: { class: 'split-group' } },
|
|
77
|
+
splitter: { tag: 'DIV', attrs: { role: 'separator' } },
|
|
78
|
+
relevanceIndicator: { tag: 'DIV', attrs: { 'aria-hidden': 'true' } },
|
|
79
|
+
colorWell: { tag: 'INPUT', attrs: { type: 'color' } },
|
|
80
|
+
helpTag: { tag: 'DIV', attrs: { class: 'help-tag' } },
|
|
81
|
+
matte: { tag: 'DIV', attrs: { class: 'matte' } },
|
|
82
|
+
dockItem: { tag: 'LI', attrs: { role: 'menuitem' } },
|
|
83
|
+
ruler: { tag: 'DIV', attrs: { class: 'ruler' } },
|
|
84
|
+
rulerMarker: { tag: 'DIV', attrs: { class: 'ruler-marker' } },
|
|
85
|
+
grid: { tag: 'DIV', attrs: { role: 'grid' } },
|
|
86
|
+
levelIndicator: { tag: 'DIV', attrs: { role: 'meter' } },
|
|
87
|
+
cell: { tag: 'LI' },
|
|
88
|
+
layoutArea: { tag: 'DIV', attrs: { class: 'layout-area' } },
|
|
89
|
+
layoutItem: { tag: 'DIV', attrs: { class: 'layout-item' } },
|
|
90
|
+
handle: { tag: 'DIV', attrs: { class: 'handle' } },
|
|
91
|
+
stepper: { tag: 'INPUT', attrs: { type: 'number', role: 'spinbutton' } },
|
|
92
|
+
tab: { tag: 'BUTTON', attrs: { role: 'tab' } },
|
|
93
|
+
touchBar: { tag: 'DIV', attrs: { class: 'touchbar' } },
|
|
94
|
+
statusItem: { tag: 'DIV', attrs: { role: 'status' } },
|
|
95
|
+
};
|
|
96
|
+
function synthesizeNativeDom(root, { source, version, metadata } = {}) {
|
|
97
|
+
return serialize(build(root, true, { source, version, metadata }));
|
|
98
|
+
}
|
|
99
|
+
exports.synthesizeNativeDom = synthesizeNativeDom;
|
|
100
|
+
function build(node, isRoot, root) {
|
|
101
|
+
var _a, _b, _c, _d;
|
|
102
|
+
const attributes = { ...((_a = node.attributes) !== null && _a !== void 0 ? _a : {}) };
|
|
103
|
+
attributes['elementTypeName'] = node.elementType;
|
|
104
|
+
const rect = node.frame
|
|
105
|
+
? { width: node.frame.width, height: node.frame.height, top: node.frame.y, left: node.frame.x }
|
|
106
|
+
: undefined;
|
|
107
|
+
const childNodes = ((_b = node.children) !== null && _b !== void 0 ? _b : []).map(child => build(child, false, root));
|
|
108
|
+
const mapping = (_c = TYPE_MAP[node.elementType]) !== null && _c !== void 0 ? _c : { tag: 'DIV' };
|
|
109
|
+
const current = { tagName: mapping.tag, attributes, childNodes, rect };
|
|
110
|
+
if (mapping.attrs)
|
|
111
|
+
Object.assign(current.attributes, mapping.attrs);
|
|
112
|
+
if (node.elementType === 'textField' || node.elementType === 'secureTextField') {
|
|
113
|
+
current.attributes['value'] = (_d = node.label) !== null && _d !== void 0 ? _d : '';
|
|
114
|
+
}
|
|
115
|
+
if (isRoot) {
|
|
116
|
+
// Wrap: the decoded root element becomes BODY (a value copy — clone attributes so
|
|
117
|
+
// a later apply() on the HTML node can't leak into BODY), and a fresh HTML node
|
|
118
|
+
// carries it plus source/version/metadata. Mirrors HTMLNode.swift:436-448.
|
|
119
|
+
const bodyNode = { ...current, attributes: { ...current.attributes } };
|
|
120
|
+
bodyNode.tagName = 'BODY';
|
|
121
|
+
current.childNodes = [bodyNode];
|
|
122
|
+
current.tagName = 'HTML';
|
|
123
|
+
if (root.source !== undefined)
|
|
124
|
+
current.source = root.source;
|
|
125
|
+
if (root.version !== undefined)
|
|
126
|
+
current.version = root.version;
|
|
127
|
+
if (root.metadata !== undefined)
|
|
128
|
+
current.metadata = root.metadata;
|
|
129
|
+
}
|
|
130
|
+
if (node.label)
|
|
131
|
+
apply(current, node.label);
|
|
132
|
+
return current;
|
|
133
|
+
}
|
|
134
|
+
/** Mirrors HTMLNode.apply(text:) — HTMLNode.swift:455-491. */
|
|
135
|
+
function apply(node, text) {
|
|
136
|
+
if (!text)
|
|
137
|
+
return;
|
|
138
|
+
switch (node.tagName) {
|
|
139
|
+
case 'INPUT':
|
|
140
|
+
node.attributes['value'] = text;
|
|
141
|
+
break;
|
|
142
|
+
case 'A':
|
|
143
|
+
if (node.childNodes.length === 0)
|
|
144
|
+
node.childNodes.push(textNode(text, node.rect));
|
|
145
|
+
else
|
|
146
|
+
node.attributes['title'] = text;
|
|
147
|
+
break;
|
|
148
|
+
case '#text':
|
|
149
|
+
node.text = text;
|
|
150
|
+
break;
|
|
151
|
+
case 'HTML':
|
|
152
|
+
node.attributes['title'] = text;
|
|
153
|
+
break;
|
|
154
|
+
case 'BUTTON':
|
|
155
|
+
// Swift only appends when empty; a labelled button with children keeps its children.
|
|
156
|
+
if (node.childNodes.length === 0)
|
|
157
|
+
node.childNodes.push(textNode(text, node.rect));
|
|
158
|
+
break;
|
|
159
|
+
case 'IMG':
|
|
160
|
+
node.attributes['src'] = text;
|
|
161
|
+
break;
|
|
162
|
+
default:
|
|
163
|
+
// A container with children shouldn't also emit a #text child over its full rect
|
|
164
|
+
// (it would confuse the Eyes UI hover/select) — set a title instead.
|
|
165
|
+
if (node.childNodes.length === 0)
|
|
166
|
+
node.childNodes.push(textNode(text, node.rect));
|
|
167
|
+
else
|
|
168
|
+
node.attributes['title'] = text;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function textNode(text, rect) {
|
|
172
|
+
return { tagName: '#text', attributes: {}, childNodes: [], rect, text };
|
|
173
|
+
}
|
|
174
|
+
function serialize(node) {
|
|
175
|
+
const out = { tagName: node.tagName, childNodes: [] };
|
|
176
|
+
if (Object.keys(node.attributes).length > 0)
|
|
177
|
+
out.attributes = node.attributes;
|
|
178
|
+
out.childNodes = node.childNodes.map(serialize);
|
|
179
|
+
if (node.rect) {
|
|
180
|
+
// The diff/ignore algo expects #text nodes to carry a `rects` array; all other
|
|
181
|
+
// tags keep a single `rect` (HTMLNode.swift:63-85).
|
|
182
|
+
if (node.tagName === '#text')
|
|
183
|
+
out.rects = [node.rect];
|
|
184
|
+
else
|
|
185
|
+
out.rect = node.rect;
|
|
186
|
+
}
|
|
187
|
+
if (node.text !== undefined)
|
|
188
|
+
out.text = node.text;
|
|
189
|
+
if (node.version !== undefined)
|
|
190
|
+
out.version = node.version;
|
|
191
|
+
if (node.source !== undefined)
|
|
192
|
+
out.source = node.source;
|
|
193
|
+
if (node.metadata !== undefined)
|
|
194
|
+
out.metadata = node.metadata;
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.takeIosDomCapture = void 0;
|
|
4
|
+
const parse_ios_page_source_1 = require("./parse-ios-page-source");
|
|
5
|
+
const synthesize_native_dom_1 = require("./synthesize-native-dom");
|
|
6
|
+
/**
|
|
7
|
+
* iOS no-NML counterpart of `takeDomCapture`: reads the accessibility hierarchy
|
|
8
|
+
* through the driver and synthesizes an Applitools DOM document from it (see
|
|
9
|
+
* docs/native-dom-without-nml-design.md). Returns undefined — an image-only
|
|
10
|
+
* check — when the driver has no page source or synthesis fails; a DOM problem
|
|
11
|
+
* must never fail the check itself.
|
|
12
|
+
*/
|
|
13
|
+
async function takeIosDomCapture({ driver, logger, }) {
|
|
14
|
+
try {
|
|
15
|
+
const pageSource = await driver.getPageSource();
|
|
16
|
+
if (!pageSource)
|
|
17
|
+
return undefined;
|
|
18
|
+
const root = (0, parse_ios_page_source_1.parseIosPageSource)(pageSource);
|
|
19
|
+
if (!root)
|
|
20
|
+
return undefined;
|
|
21
|
+
return JSON.stringify((0, synthesize_native_dom_1.synthesizeNativeDom)(root, { source: 'mobile-ios-appium' }));
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
logger.error('failed to synthesize DOM from the iOS page source', error);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.takeIosDomCapture = takeIosDomCapture;
|
|
@@ -25,10 +25,11 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
exports.takeScreenshots = void 0;
|
|
27
27
|
const take_dom_capture_1 = require("./take-dom-capture");
|
|
28
|
+
const take_ios_dom_capture_1 = require("./take-ios-dom-capture");
|
|
28
29
|
const take_screenshot_1 = require("../../automation/utils/take-screenshot");
|
|
29
30
|
const utils = __importStar(require("@applitools/utils"));
|
|
30
31
|
async function takeScreenshots({ driver, settings, logger, }) {
|
|
31
|
-
var _a, _b;
|
|
32
|
+
var _a, _b, _c, _d;
|
|
32
33
|
const screenshot = await logger.withTime((0, take_screenshot_1.takeScreenshot)({ driver, settings, logger }), 'takeScreenshot');
|
|
33
34
|
const name = await logger.withTime(driver.getTitle(), 'getTitle');
|
|
34
35
|
const source = await logger.withTime(driver.getUrl(), 'getUrl');
|
|
@@ -52,6 +53,27 @@ async function takeScreenshots({ driver, settings, logger, }) {
|
|
|
52
53
|
return undefined;
|
|
53
54
|
}), 'takeDomCapture');
|
|
54
55
|
}
|
|
56
|
+
else if (environment.isNative &&
|
|
57
|
+
environment.isIOS &&
|
|
58
|
+
!((_c = environment.applitoolsLib) === null || _c === void 0 ? void 0 : _c.instrumented) &&
|
|
59
|
+
!settings.webview &&
|
|
60
|
+
settings.domSettings) {
|
|
61
|
+
// No-NML iOS app: synthesize a DOM from the accessibility hierarchy so the
|
|
62
|
+
// check still gets layout matching / dashboard hover / RCA. Android pending.
|
|
63
|
+
const dom = await logger.withTime((0, take_ios_dom_capture_1.takeIosDomCapture)({ driver, logger }), 'takeIosDomCapture');
|
|
64
|
+
if (dom) {
|
|
65
|
+
baseTarget.dom = dom;
|
|
66
|
+
// The native screenshot is cropped below the status bar while the page-source
|
|
67
|
+
// DOM stays in full-screen coordinates. Shift the reported location down by the
|
|
68
|
+
// band the screenshoter actually cropped, so the server seats the DOM correctly
|
|
69
|
+
// (same fix as the iOS SDK's classic no-NML path). Using the screenshoter's own
|
|
70
|
+
// reported offset keeps this consistent with the pixels for viewport, region and
|
|
71
|
+
// framed captures alike. Rounded — the server rejects fractional locations.
|
|
72
|
+
if (screenshot.statusBarOffset) {
|
|
73
|
+
baseTarget.locationInViewport = utils.geometry.round(utils.geometry.offset((_d = baseTarget.locationInViewport) !== null && _d !== void 0 ? _d : { x: 0, y: 0 }, { x: 0, y: screenshot.statusBarOffset }));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
55
77
|
if (settings.calculateView) {
|
|
56
78
|
const scrollingElement = await driver.mainContext.getScrollingElement();
|
|
57
79
|
const scrollingOffset = !scrollingElement || environment.isNative ? { x: 0, y: 0 } : await scrollingElement.getScrollOffset();
|
package/dist/open-eyes.js
CHANGED
|
@@ -60,8 +60,8 @@ exports.sanitizeFinding = sanitizeFinding;
|
|
|
60
60
|
function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDuplicateTests, core, cores, spec, environment, logger: mainLogger, asyncCache, concurrency, useServerConcurrency = false, cwd = process.cwd(), }) {
|
|
61
61
|
let shouldPrintConcurrencyWarning = true;
|
|
62
62
|
return async function openEyes({ type = defaultType, settings: openSettings, config, target, logger = mainLogger, }) {
|
|
63
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
|
|
64
|
-
var
|
|
63
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3;
|
|
64
|
+
var _4, _5, _6, _7, _8;
|
|
65
65
|
logger = logger.extend(mainLogger, { tags: [`eyes-${type}-${utils.general.shortid()}`] });
|
|
66
66
|
logger.log(`Opening Eyes in directory "${cwd}"`);
|
|
67
67
|
const settings = { environments: config === null || config === void 0 ? void 0 : config.check.environments, ...config === null || config === void 0 ? void 0 : config.open, ...openSettings };
|
|
@@ -70,11 +70,11 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
|
|
|
70
70
|
logger.mask(eyesServerSettings.eyesServerUrl);
|
|
71
71
|
(_a = settings.userTestId) !== null && _a !== void 0 ? _a : (settings.userTestId = `${settings.testName}--${utils.general.guid()}`);
|
|
72
72
|
settings.batch = { ...batch, ...settings.batch };
|
|
73
|
-
(_b = (
|
|
74
|
-
(_d = (
|
|
75
|
-
(_e = (
|
|
76
|
-
(_f = (
|
|
77
|
-
(_g = (
|
|
73
|
+
(_b = (_4 = settings.batch).id) !== null && _b !== void 0 ? _b : (_4.id = (_c = utils.general.getEnvValue('BATCH_ID')) !== null && _c !== void 0 ? _c : `generated-${utils.general.guid()}`);
|
|
74
|
+
(_d = (_5 = settings.batch).buildId) !== null && _d !== void 0 ? _d : (_5.buildId = utils.general.getEnvValue('BATCH_BUILD_ID'));
|
|
75
|
+
(_e = (_6 = settings.batch).name) !== null && _e !== void 0 ? _e : (_6.name = utils.general.getEnvValue('BATCH_NAME'));
|
|
76
|
+
(_f = (_7 = settings.batch).sequenceName) !== null && _f !== void 0 ? _f : (_7.sequenceName = utils.general.getEnvValue('BATCH_SEQUENCE'));
|
|
77
|
+
(_g = (_8 = settings.batch).notifyOnCompletion) !== null && _g !== void 0 ? _g : (_8.notifyOnCompletion = utils.general.getEnvValue('BATCH_NOTIFY', 'boolean'));
|
|
78
78
|
(_h = settings.keepBatchOpen) !== null && _h !== void 0 ? _h : (settings.keepBatchOpen = utils.general.getEnvValue('DONT_CLOSE_BATCHES', 'boolean'));
|
|
79
79
|
(_j = settings.branchName) !== null && _j !== void 0 ? _j : (settings.branchName = (_k = utils.general.getEnvValue('BRANCH')) !== null && _k !== void 0 ? _k : utils.general.getEnvValue('BRANCH_NAME'));
|
|
80
80
|
(_l = settings.parentBranchName) !== null && _l !== void 0 ? _l : (settings.parentBranchName = (_m = utils.general.getEnvValue('PARENT_BRANCH')) !== null && _m !== void 0 ? _m : utils.general.getEnvValue('PARENT_BRANCH_NAME'));
|
|
@@ -90,14 +90,18 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
|
|
|
90
90
|
logger.warn(`latestCommitInfo.timestamp is an invalid ISO date string: ${settings.latestCommitInfo.timestamp}`);
|
|
91
91
|
settings.latestCommitInfo = undefined;
|
|
92
92
|
}
|
|
93
|
+
// Bare git refs from the scm integration. Kept out of `settings` because that pair is the
|
|
94
|
+
// session's identity on the wire; these are only a `git merge-base` argument.
|
|
95
|
+
let scmGitRefs;
|
|
93
96
|
if (process.env.CI && (isRealBatchId(settings.batch.id) || settings.latestCommitInfo)) {
|
|
94
|
-
await populateAutoScmInfo(isRealBatchId(settings.batch.id) ? settings.batch.id : settings.latestCommitInfo.sha);
|
|
97
|
+
scmGitRefs = await populateAutoScmInfo(isRealBatchId(settings.batch.id) ? settings.batch.id : settings.latestCommitInfo.sha);
|
|
95
98
|
}
|
|
96
99
|
if (settings.ignoreGitBranching) {
|
|
97
100
|
settings.gitBranchingTimestamp = undefined;
|
|
98
101
|
}
|
|
99
102
|
else if (!settings.gitBranchingTimestamp) {
|
|
100
|
-
const
|
|
103
|
+
const branchName = (_x = settings.branchName) !== null && _x !== void 0 ? _x : scmGitRefs === null || scmGitRefs === void 0 ? void 0 : scmGitRefs.branchName;
|
|
104
|
+
const parentBranchName = (_y = settings.parentBranchName) !== null && _y !== void 0 ? _y : scmGitRefs === null || scmGitRefs === void 0 ? void 0 : scmGitRefs.parentBranchName;
|
|
101
105
|
if (branchName && parentBranchName && branchName !== parentBranchName) {
|
|
102
106
|
settings.gitBranchingTimestamp = await (0, extract_git_info_1.extractBranchingTimestamp)({
|
|
103
107
|
branchName,
|
|
@@ -112,7 +116,7 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
|
|
|
112
116
|
settings.gitBranchingTimestamp = undefined;
|
|
113
117
|
}
|
|
114
118
|
await (0, seam_1.applyFigmaBaselines)(settings, logger);
|
|
115
|
-
(
|
|
119
|
+
(_z = settings.bypassCSP) !== null && _z !== void 0 ? _z : (settings.bypassCSP = utils.general.getEnvValue('BYPASS_CSP', 'boolean') === true || undefined);
|
|
116
120
|
const driver = target && (await (0, driver_1.makeDriver)({ spec, driver: target, logger, customConfig: settings }));
|
|
117
121
|
if (settings.bypassCSP) {
|
|
118
122
|
logger.warn('CSP bypass is enabled.');
|
|
@@ -121,11 +125,11 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
|
|
|
121
125
|
const driverEnvironment = await (driver === null || driver === void 0 ? void 0 : driver.getEnvironment());
|
|
122
126
|
const driverUrl = await (driver === null || driver === void 0 ? void 0 : driver.getDriverUrl());
|
|
123
127
|
const multiDeviceTargets = [];
|
|
124
|
-
for (const checkEnv of (
|
|
125
|
-
if (utils.types.has(checkEnv, 'iosDeviceInfo') && ((
|
|
128
|
+
for (const checkEnv of (_0 = settings.environments) !== null && _0 !== void 0 ? _0 : []) {
|
|
129
|
+
if (utils.types.has(checkEnv, 'iosDeviceInfo') && ((_1 = driverEnvironment === null || driverEnvironment === void 0 ? void 0 : driverEnvironment.applitoolsLib) === null || _1 === void 0 ? void 0 : _1.instrumented)) {
|
|
126
130
|
multiDeviceTargets.push({
|
|
127
131
|
deviceInfo: checkEnv.iosDeviceInfo.deviceName,
|
|
128
|
-
orientation: (
|
|
132
|
+
orientation: (_2 = checkEnv.iosDeviceInfo.screenOrientation) !== null && _2 !== void 0 ? _2 : 'portrait',
|
|
129
133
|
});
|
|
130
134
|
}
|
|
131
135
|
}
|
|
@@ -155,7 +159,7 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
|
|
|
155
159
|
browserVersion: driverEnvironment === null || driverEnvironment === void 0 ? void 0 : driverEnvironment.browserVersion,
|
|
156
160
|
platformName: driverEnvironment === null || driverEnvironment === void 0 ? void 0 : driverEnvironment.platformName,
|
|
157
161
|
platformVersion: driverEnvironment === null || driverEnvironment === void 0 ? void 0 : driverEnvironment.platformVersion,
|
|
158
|
-
isApplitoolsLib: (
|
|
162
|
+
isApplitoolsLib: (_3 = driverEnvironment === null || driverEnvironment === void 0 ? void 0 : driverEnvironment.applitoolsLib) === null || _3 === void 0 ? void 0 : _3.instrumented,
|
|
159
163
|
isEC: driverEnvironment === null || driverEnvironment === void 0 ? void 0 : driverEnvironment.isEC,
|
|
160
164
|
},
|
|
161
165
|
driverUrl,
|
|
@@ -193,8 +197,8 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
|
|
|
193
197
|
}); // TODO solve the types issue
|
|
194
198
|
/////// END FUNCTION BODY ///////////////////
|
|
195
199
|
async function populateAutoScmInfo(batchId) {
|
|
196
|
-
var _a, _b
|
|
197
|
-
var
|
|
200
|
+
var _a, _b;
|
|
201
|
+
var _c;
|
|
198
202
|
const branchName = await (0, extract_git_info_1.extractGitBranch)({ execOptions: { cwd }, logger });
|
|
199
203
|
const { owner: repoOwner, name: repoName } = await (0, extract_git_info_1.extractGitRepo)({ execOptions: { cwd }, logger });
|
|
200
204
|
const buildId = await (0, extract_git_info_1.extractBuildIdFromCI)();
|
|
@@ -210,10 +214,10 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
|
|
|
210
214
|
if (scmInfo) {
|
|
211
215
|
(_a = settings.batch) !== null && _a !== void 0 ? _a : (settings.batch = {});
|
|
212
216
|
settings.batch.id = isRealBatchId(settings.batch.id) ? settings.batch.id : batchId;
|
|
213
|
-
(_b = (
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
(_b = (_c = settings.batch).buildId) !== null && _b !== void 0 ? _b : (_c.buildId = buildId);
|
|
218
|
+
// Not setting branchName/parentBranchName is the point: the session's value overrides
|
|
219
|
+
// the batch's qualified name (SessionStartInfo.GetBranchName), which the merge keys on.
|
|
220
|
+
return { branchName: scmInfo.scmSourceBranch, parentBranchName: scmInfo.scmTargetBranch };
|
|
217
221
|
}
|
|
218
222
|
}
|
|
219
223
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applitools/core",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.70.0",
|
|
4
4
|
"homepage": "https://applitools.com",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/applitools/eyes.sdk.javascript1/issues"
|
|
@@ -48,35 +48,38 @@
|
|
|
48
48
|
"build": "yarn build:dist",
|
|
49
49
|
"build:dist": "run --top-level tsc --project ./tsconfig.build.json",
|
|
50
50
|
"test": "run --top-level mocha './test/**/*.spec.ts' --exclude 'test/e2e/mocha-sync/**' --parallel --jobs ${MOCHA_JOBS:-15} --exit --require ./test/mocha-global-setup.js",
|
|
51
|
-
"test:local": "MOCHA_OMIT_TAGS=sauce,browserstack run test",
|
|
51
|
+
"test:local": "MOCHA_OMIT_TAGS=sauce,browserstack,ios-simulator run test",
|
|
52
52
|
"test:sauce": "MOCHA_ONLY_TAGS=sauce,browserstack run test",
|
|
53
53
|
"test:bin": "MOCHA_GROUP=bin run --top-level mocha './test/bin/**/*.spec.ts' --parallel --jobs ${MOCHA_JOBS:-15} --require ./test/mocha-global-setup.js",
|
|
54
|
-
"test:e2e": "MOCHA_OMIT_TAGS=http2 MOCHA_GROUP=e2e run --top-level mocha './test/e2e/**/*.spec.ts' --exclude 'test/e2e/mocha-sync/**' --parallel --jobs ${MOCHA_JOBS:-15} --exit --require ./test/mocha-global-setup.js",
|
|
55
|
-
"test:e2e:http2": "APPLITOOLS_HTTP_VERSION=2 MOCHA_OMIT_TAGS=http1,sauce,browserstack MOCHA_GROUP=e2e run --top-level mocha './test/e2e/**/*.spec.ts' --exclude 'test/e2e/mocha-sync/**' --parallel --jobs ${MOCHA_JOBS:-15} --exit --require ./test/mocha-global-setup.js",
|
|
54
|
+
"test:e2e": "MOCHA_OMIT_TAGS=http2,ios-simulator MOCHA_GROUP=e2e run --top-level mocha './test/e2e/**/*.spec.ts' --exclude 'test/e2e/mocha-sync/**' --parallel --jobs ${MOCHA_JOBS:-15} --exit --require ./test/mocha-global-setup.js",
|
|
55
|
+
"test:e2e:http2": "APPLITOOLS_HTTP_VERSION=2 MOCHA_OMIT_TAGS=http1,sauce,browserstack,ios-simulator MOCHA_GROUP=e2e run --top-level mocha './test/e2e/**/*.spec.ts' --exclude 'test/e2e/mocha-sync/**' --parallel --jobs ${MOCHA_JOBS:-15} --exit --require ./test/mocha-global-setup.js",
|
|
56
56
|
"test:it": "MOCHA_GROUP=it run --top-level mocha './test/it/**/*.spec.ts' --require ./test/mocha-global-setup.js",
|
|
57
57
|
"test:e2e:sync": "MOCHA_GROUP=e2e SYNC=true run --top-level mocha './test/e2e/mocha-sync/**/*.spec.ts' --exit --require ./test/mocha-global-setup.js",
|
|
58
58
|
"test:unit": "MOCHA_GROUP=unit run --top-level mocha './test/unit/**/*.spec.ts' --require ./test/mocha-global-setup.js",
|
|
59
59
|
"setup": "run --top-level browsers:setup && run --top-level xvfb:setup && run playwright:setup",
|
|
60
60
|
"playwright:setup": "node -e \"var v=parseInt(process.version.slice(1));if(v>=18){require('child_process').execSync('yarn playwright install --with-deps firefox',{stdio:'inherit'})}else{console.log('Skipping playwright install (Node '+process.version+')')}\"",
|
|
61
|
-
"setup:standalone": "sh -c 'yarn chromedriver --port=4444 --verbose &'"
|
|
61
|
+
"setup:standalone": "sh -c 'yarn chromedriver --port=4444 --verbose &'",
|
|
62
|
+
"setup:ios-simulator": "npx --yes appium@3 driver install xcuitest && cd ../../../test/apps/ios/IOSTestApp && xcodebuild -project IOSTestApp.xcodeproj -target IOSTestApp -configuration Release -sdk iphonesimulator CODE_SIGNING_ALLOWED=NO SYMROOT=build build",
|
|
63
|
+
"test:ios-simulator": "MOCHA_ONLY_TAGS=ios-simulator MOCHA_GROUP=ios-simulator run --top-level mocha ./test/e2e/classic/webview.spec.ts --exit --require ./test/mocha-global-setup.js"
|
|
62
64
|
},
|
|
63
65
|
"dependencies": {
|
|
64
66
|
"@applitools/core-base": "1.38.2",
|
|
65
67
|
"@applitools/dom-capture": "11.8.5",
|
|
66
|
-
"@applitools/dom-snapshot": "4.18.
|
|
67
|
-
"@applitools/driver": "1.26.
|
|
68
|
-
"@applitools/ec-client": "1.12.
|
|
68
|
+
"@applitools/dom-snapshot": "4.18.2",
|
|
69
|
+
"@applitools/driver": "1.26.10",
|
|
70
|
+
"@applitools/ec-client": "1.12.45",
|
|
69
71
|
"@applitools/image": "1.3.2",
|
|
70
72
|
"@applitools/logger": "2.3.1",
|
|
71
|
-
"@applitools/nml-client": "1.12.
|
|
73
|
+
"@applitools/nml-client": "1.12.2",
|
|
72
74
|
"@applitools/req": "1.11.3",
|
|
73
|
-
"@applitools/screenshoter": "3.12.
|
|
75
|
+
"@applitools/screenshoter": "3.12.31",
|
|
74
76
|
"@applitools/snippets": "2.9.7",
|
|
75
77
|
"@applitools/socket": "1.3.17",
|
|
76
|
-
"@applitools/ufg-client": "1.23.
|
|
78
|
+
"@applitools/ufg-client": "1.23.3",
|
|
77
79
|
"@applitools/utils": "1.15.2",
|
|
78
80
|
"abort-controller": "3.0.0",
|
|
79
81
|
"chalk": "4.1.2",
|
|
82
|
+
"fast-xml-parser": "5.11.1",
|
|
80
83
|
"node-fetch": "2.6.7",
|
|
81
84
|
"semver": "7.6.2",
|
|
82
85
|
"shell-quote": "^1.8.4",
|
|
@@ -84,10 +87,10 @@
|
|
|
84
87
|
},
|
|
85
88
|
"devDependencies": {
|
|
86
89
|
"@applitools/bongo": "^5.10.0",
|
|
87
|
-
"@applitools/spec-driver-playwright": "^1.9.
|
|
88
|
-
"@applitools/spec-driver-puppeteer": "^1.8.
|
|
89
|
-
"@applitools/spec-driver-selenium": "^1.8.
|
|
90
|
-
"@applitools/spec-driver-webdriver": "^1.6.
|
|
90
|
+
"@applitools/spec-driver-playwright": "^1.9.10",
|
|
91
|
+
"@applitools/spec-driver-puppeteer": "^1.8.10",
|
|
92
|
+
"@applitools/spec-driver-selenium": "^1.8.10",
|
|
93
|
+
"@applitools/spec-driver-webdriver": "^1.6.10",
|
|
91
94
|
"@applitools/test-server": "^1.4.7",
|
|
92
95
|
"@applitools/test-utils": "^1.5.17",
|
|
93
96
|
"@applitools/tunnel-client": "^1.12.4",
|
|
@@ -13,6 +13,8 @@ export type Screenshot<TSpec extends SpecType> = {
|
|
|
13
13
|
selector?: Selector;
|
|
14
14
|
regions: Region[];
|
|
15
15
|
}[];
|
|
16
|
+
/** Points the native status bar was cropped from the image top (0 if kept or on web). */
|
|
17
|
+
statusBarOffset: number;
|
|
16
18
|
};
|
|
17
19
|
export declare function takeScreenshot<TSpec extends SpecType>({ driver, settings, logger, }: {
|
|
18
20
|
driver: Driver<TSpec>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesizes an Applitools DOM-capture document from a native accessibility
|
|
3
|
+
* tree — a port of the iOS SDK's `HTMLNode.swift` mapping that must stay in sync
|
|
4
|
+
* with it. See core/llm-docs/CLASSIC.md ("iOS DOM Synthesis").
|
|
5
|
+
*/
|
|
6
|
+
export type NativeElementNode = {
|
|
7
|
+
/** XCUIElementType case-name form, e.g. 'button', 'staticText', 'application'. */
|
|
8
|
+
elementType: string;
|
|
9
|
+
/** Element frame in points; origin is top-left. */
|
|
10
|
+
frame?: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
};
|
|
16
|
+
/** Accessibility label (iOS gives '' for unlabeled; the parser should map ''->undefined). */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Any pre-existing attributes (usually none from the Appium path). */
|
|
19
|
+
attributes?: Record<string, string>;
|
|
20
|
+
children?: NativeElementNode[];
|
|
21
|
+
};
|
|
22
|
+
type Rect = {
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
top: number;
|
|
26
|
+
left: number;
|
|
27
|
+
};
|
|
28
|
+
export type SyntheticDomNode = {
|
|
29
|
+
tagName: string;
|
|
30
|
+
attributes?: Record<string, string>;
|
|
31
|
+
childNodes: SyntheticDomNode[];
|
|
32
|
+
rect?: Rect;
|
|
33
|
+
rects?: Rect[];
|
|
34
|
+
text?: string;
|
|
35
|
+
version?: string;
|
|
36
|
+
source?: string;
|
|
37
|
+
metadata?: unknown;
|
|
38
|
+
};
|
|
39
|
+
export declare function synthesizeNativeDom(root: NativeElementNode, { source, version, metadata }?: {
|
|
40
|
+
source?: string;
|
|
41
|
+
version?: string;
|
|
42
|
+
metadata?: unknown;
|
|
43
|
+
}): SyntheticDomNode;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Logger } from '@applitools/logger';
|
|
2
|
+
import { type SpecType, type Driver } from '@applitools/driver';
|
|
3
|
+
/**
|
|
4
|
+
* iOS no-NML counterpart of `takeDomCapture`: reads the accessibility hierarchy
|
|
5
|
+
* through the driver and synthesizes an Applitools DOM document from it (see
|
|
6
|
+
* docs/native-dom-without-nml-design.md). Returns undefined — an image-only
|
|
7
|
+
* check — when the driver has no page source or synthesis fails; a DOM problem
|
|
8
|
+
* must never fail the check itself.
|
|
9
|
+
*/
|
|
10
|
+
export declare function takeIosDomCapture<TSpec extends SpecType>({ driver, logger, }: {
|
|
11
|
+
driver: Driver<TSpec>;
|
|
12
|
+
logger: Logger;
|
|
13
|
+
}): Promise<string | undefined>;
|