@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 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 (`&#10;` 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 _2, _3, _4, _5, _6;
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 = (_2 = settings.batch).id) !== null && _b !== void 0 ? _b : (_2.id = (_c = utils.general.getEnvValue('BATCH_ID')) !== null && _c !== void 0 ? _c : `generated-${utils.general.guid()}`);
74
- (_d = (_3 = settings.batch).buildId) !== null && _d !== void 0 ? _d : (_3.buildId = utils.general.getEnvValue('BATCH_BUILD_ID'));
75
- (_e = (_4 = settings.batch).name) !== null && _e !== void 0 ? _e : (_4.name = utils.general.getEnvValue('BATCH_NAME'));
76
- (_f = (_5 = settings.batch).sequenceName) !== null && _f !== void 0 ? _f : (_5.sequenceName = utils.general.getEnvValue('BATCH_SEQUENCE'));
77
- (_g = (_6 = settings.batch).notifyOnCompletion) !== null && _g !== void 0 ? _g : (_6.notifyOnCompletion = utils.general.getEnvValue('BATCH_NOTIFY', 'boolean'));
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 { branchName, parentBranchName } = settings;
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
- (_x = settings.bypassCSP) !== null && _x !== void 0 ? _x : (settings.bypassCSP = utils.general.getEnvValue('BYPASS_CSP', 'boolean') === true || undefined);
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 (_y = settings.environments) !== null && _y !== void 0 ? _y : []) {
125
- if (utils.types.has(checkEnv, 'iosDeviceInfo') && ((_z = driverEnvironment === null || driverEnvironment === void 0 ? void 0 : driverEnvironment.applitoolsLib) === null || _z === void 0 ? void 0 : _z.instrumented)) {
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: (_0 = checkEnv.iosDeviceInfo.screenOrientation) !== null && _0 !== void 0 ? _0 : 'portrait',
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: (_1 = driverEnvironment === null || driverEnvironment === void 0 ? void 0 : driverEnvironment.applitoolsLib) === null || _1 === void 0 ? void 0 : _1.instrumented,
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, _c, _d;
197
- var _e;
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 = (_e = settings.batch).buildId) !== null && _b !== void 0 ? _b : (_e.buildId = buildId);
214
- // Takes data if it was not set by env. This is the priority according to yotam from BE
215
- (_c = settings.branchName) !== null && _c !== void 0 ? _c : (settings.branchName = scmInfo.scmSourceBranch || undefined);
216
- (_d = settings.parentBranchName) !== null && _d !== void 0 ? _d : (settings.parentBranchName = scmInfo.scmTargetBranch || undefined);
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.69.1",
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.1",
67
- "@applitools/driver": "1.26.9",
68
- "@applitools/ec-client": "1.12.44",
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.1",
73
+ "@applitools/nml-client": "1.12.2",
72
74
  "@applitools/req": "1.11.3",
73
- "@applitools/screenshoter": "3.12.30",
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.2",
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.9",
88
- "@applitools/spec-driver-puppeteer": "^1.8.9",
89
- "@applitools/spec-driver-selenium": "^1.8.9",
90
- "@applitools/spec-driver-webdriver": "^1.6.9",
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,2 @@
1
+ import type { NativeElementNode } from './synthesize-native-dom';
2
+ export declare function parseIosPageSource(xml: string): NativeElementNode | undefined;
@@ -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>;