@applitools/core 4.67.2 → 4.68.1

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/dist/autonomous/create-render-results.js +6 -1
  3. package/dist/autonomous/start-renders.js +16 -7
  4. package/dist/check-and-close.js +7 -1
  5. package/dist/check.js +8 -1
  6. package/dist/classic/utils/extract-default-environments.js +17 -2
  7. package/dist/figma/client/figma-req.js +46 -0
  8. package/dist/figma/client/figma-rest.js +189 -0
  9. package/dist/figma/client/index.js +21 -0
  10. package/dist/figma/figma-images.js +62 -0
  11. package/dist/figma/figma.js +168 -0
  12. package/dist/figma/index.js +27 -0
  13. package/dist/figma/run-figma-check.js +82 -0
  14. package/dist/figma/seam.js +91 -0
  15. package/dist/figma/target-config.js +52 -0
  16. package/dist/figma/types.js +2 -0
  17. package/dist/figma/utils.js +128 -0
  18. package/dist/offline/run-offline-snapshots.js +18 -3
  19. package/dist/open-eyes.js +4 -2
  20. package/dist/ufg/build-accessibility-report-options.js +40 -0
  21. package/dist/ufg/check-and-close.js +14 -5
  22. package/dist/ufg/check.js +10 -1
  23. package/package.json +23 -22
  24. package/types/automation/types.d.ts +9 -0
  25. package/types/autonomous/types.d.ts +1 -0
  26. package/types/check-and-close.d.ts +3 -2
  27. package/types/check.d.ts +3 -2
  28. package/types/figma/client/figma-req.d.ts +9 -0
  29. package/types/figma/client/figma-rest.d.ts +24 -0
  30. package/types/figma/client/index.d.ts +2 -0
  31. package/types/figma/figma-images.d.ts +6 -0
  32. package/types/figma/figma.d.ts +21 -0
  33. package/types/figma/index.d.ts +8 -0
  34. package/types/figma/run-figma-check.d.ts +13 -0
  35. package/types/figma/seam.d.ts +12 -0
  36. package/types/figma/target-config.d.ts +7 -0
  37. package/types/figma/types.d.ts +69 -0
  38. package/types/figma/utils.d.ts +26 -0
  39. package/types/types.d.ts +1 -0
  40. package/types/ufg/build-accessibility-report-options.d.ts +13 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,192 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.68.1](https://github.com/Applitools-Dev/sdk/compare/js/core@4.68.0...js/core@4.68.1) (2026-08-10)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
9
+ * omit the empty os from the figma reference env so the design baseline persists | AD-15527 ([#4067](https://github.com/Applitools-Dev/sdk/issues/4067)) ([8395814](https://github.com/Applitools-Dev/sdk/commit/8395814feb6b20eee1c1322dc1539aa4642663d8))
10
+ * use an authoritative CSS viewport in classic mode | FLD-4814 ([#4047](https://github.com/Applitools-Dev/sdk/issues/4047)) ([c46497e](https://github.com/Applitools-Dev/sdk/commit/c46497e573fd7fd2da4913452239beea082b148f))
11
+
12
+
13
+ ### Dependencies
14
+
15
+ * @applitools/utils bumped to 1.15.2
16
+ #### Bug Fixes
17
+
18
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
19
+ * @applitools/logger bumped to 2.3.1
20
+ #### Bug Fixes
21
+
22
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
23
+
24
+
25
+
26
+ * @applitools/dom-shared bumped to 1.2.3
27
+ #### Bug Fixes
28
+
29
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
30
+ * @applitools/dom-snapshot bumped to 4.17.9
31
+ #### Bug Fixes
32
+
33
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
34
+
35
+
36
+
37
+ * @applitools/socket bumped to 1.3.16
38
+ #### Bug Fixes
39
+
40
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
41
+
42
+
43
+
44
+ * @applitools/req bumped to 1.11.3
45
+ #### Bug Fixes
46
+
47
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
48
+
49
+
50
+
51
+ * @applitools/image bumped to 1.3.1
52
+ #### Bug Fixes
53
+
54
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
55
+
56
+
57
+
58
+ * @applitools/snippets bumped to 2.9.6
59
+ #### Bug Fixes
60
+
61
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
62
+
63
+
64
+
65
+ * @applitools/dom-capture bumped to 11.8.5
66
+ #### Bug Fixes
67
+
68
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
69
+
70
+
71
+
72
+ * @applitools/driver bumped to 1.26.8
73
+ #### Bug Fixes
74
+
75
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
76
+ * use an authoritative CSS viewport in classic mode | FLD-4814 ([#4047](https://github.com/Applitools-Dev/sdk/issues/4047)) ([c46497e](https://github.com/Applitools-Dev/sdk/commit/c46497e573fd7fd2da4913452239beea082b148f))
77
+
78
+
79
+
80
+ * @applitools/spec-driver-webdriver bumped to 1.6.8
81
+ #### Bug Fixes
82
+
83
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
84
+
85
+
86
+
87
+ * @applitools/spec-driver-selenium bumped to 1.8.8
88
+ #### Bug Fixes
89
+
90
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
91
+
92
+
93
+
94
+ * @applitools/spec-driver-playwright bumped to 1.9.8
95
+ #### Bug Fixes
96
+
97
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
98
+
99
+
100
+
101
+ * @applitools/spec-driver-puppeteer bumped to 1.8.8
102
+ #### Bug Fixes
103
+
104
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
105
+
106
+
107
+
108
+ * @applitools/screenshoter bumped to 3.12.28
109
+ #### Bug Fixes
110
+
111
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
112
+
113
+
114
+
115
+ * @applitools/nml-client bumped to 1.11.40
116
+ #### Bug Fixes
117
+
118
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
119
+
120
+
121
+
122
+ * @applitools/tunnel-client bumped to 1.12.3
123
+ #### Bug Fixes
124
+
125
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
126
+
127
+
128
+
129
+ * @applitools/ufg-client bumped to 1.23.1
130
+ #### Bug Fixes
131
+
132
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
133
+
134
+
135
+
136
+ * @applitools/core-base bumped to 1.38.1
137
+ #### Bug Fixes
138
+
139
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
140
+
141
+
142
+
143
+ * @applitools/ec-client bumped to 1.12.42
144
+ #### Bug Fixes
145
+
146
+ * apply the figma UFG environments rewrite to the renders | AD-15528 ([#4072](https://github.com/Applitools-Dev/sdk/issues/4072)) ([bd6b143](https://github.com/Applitools-Dev/sdk/commit/bd6b143f855750ae93701f8e44da6129ff7ae84c))
147
+
148
+
149
+
150
+ * @applitools/test-server bumped to 1.4.7
151
+
152
+
153
+ ## [4.68.0](https://github.com/Applitools-Dev/sdk/compare/js/core@4.67.2...js/core@4.68.0) (2026-08-05)
154
+
155
+
156
+ ### Features
157
+
158
+ * add figmaOptions open config to all SDKs | AD-15464 ([#4052](https://github.com/Applitools-Dev/sdk/issues/4052)) ([cff2a2d](https://github.com/Applitools-Dev/sdk/commit/cff2a2d2f3d905dbfd9e12a9b7b26c61c2348935))
159
+ * figmaBaselines first-class open-config, in-repo resolver, all SDKs | AD-15462 ([#4021](https://github.com/Applitools-Dev/sdk/issues/4021)) ([998b3b3](https://github.com/Applitools-Dev/sdk/commit/998b3b38486669d2997f9eb11316067abbe85230))
160
+ * forward accessibilityReportOptions (axe-core) from render-info into the UFG render request | AD-15104 ([#4004](https://github.com/Applitools-Dev/sdk/issues/4004)) ([eef1b59](https://github.com/Applitools-Dev/sdk/commit/eef1b59feac63a488fdb9e7de73d3063cf43c8ca))
161
+
162
+
163
+ ### Dependencies
164
+
165
+ * @applitools/image bumped to 1.3.0
166
+ #### Features
167
+
168
+ * figmaBaselines first-class open-config, in-repo resolver, all SDKs | AD-15462 ([#4021](https://github.com/Applitools-Dev/sdk/issues/4021)) ([998b3b3](https://github.com/Applitools-Dev/sdk/commit/998b3b38486669d2997f9eb11316067abbe85230))
169
+ * @applitools/ufg-client bumped to 1.23.0
170
+ #### Features
171
+
172
+ * forward accessibilityReportOptions (axe-core) from render-info into the UFG render request | AD-15104 ([#4004](https://github.com/Applitools-Dev/sdk/issues/4004)) ([eef1b59](https://github.com/Applitools-Dev/sdk/commit/eef1b59feac63a488fdb9e7de73d3063cf43c8ca))
173
+
174
+
175
+
176
+ * @applitools/screenshoter bumped to 3.12.27
177
+
178
+ * @applitools/nml-client bumped to 1.11.39
179
+
180
+ * @applitools/core-base bumped to 1.38.0
181
+ #### Features
182
+
183
+ * forward accessibilityReportOptions (axe-core) from render-info into the UFG render request | AD-15104 ([#4004](https://github.com/Applitools-Dev/sdk/issues/4004)) ([eef1b59](https://github.com/Applitools-Dev/sdk/commit/eef1b59feac63a488fdb9e7de73d3063cf43c8ca))
184
+
185
+
186
+
187
+ * @applitools/ec-client bumped to 1.12.41
188
+
189
+
3
190
  ## [4.67.2](https://github.com/Applitools-Dev/sdk/compare/js/core@4.67.1...js/core@4.67.2) (2026-07-30)
4
191
 
5
192
 
@@ -23,6 +23,7 @@ function makeCreateRenderResults({ logger }) {
23
23
  }
24
24
  });
25
25
  return results.map(({ selectorRegions, renderId, status, error, ...baseTarget }, index) => {
26
+ var _a;
26
27
  const { elementReferences: selectors, getBaseCheckSettings } = (0, to_base_check_settings_1.toBaseCheckSettings)({
27
28
  settings: renders[index].settings,
28
29
  });
@@ -38,7 +39,11 @@ function makeCreateRenderResults({ logger }) {
38
39
  return {
39
40
  status,
40
41
  error,
41
- target: { ...baseTarget, isTransformed: true },
42
+ target: {
43
+ ...baseTarget,
44
+ isTransformed: true,
45
+ accessibilityReportLocation: (_a = baseTarget.accessibilityReportLocation) !== null && _a !== void 0 ? _a : renders[index].accessibilityReportLocation,
46
+ },
42
47
  settings: baseSettings,
43
48
  environment: renders[index].environment,
44
49
  account: renders[index].account,
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.makeStartRenders = void 0;
4
4
  const to_base_check_settings_1 = require("../automation/utils/to-base-check-settings");
5
+ const build_accessibility_report_options_1 = require("../ufg/build-accessibility-report-options");
5
6
  function makeStartRenders({ core, logger: defaultLogger }) {
6
7
  return async function startRenders({ targets, logger = defaultLogger, }) {
7
8
  const clients = await targets.reduce(async (promise, target) => {
@@ -25,9 +26,12 @@ function makeStartRenders({ core, logger: defaultLogger }) {
25
26
  });
26
27
  }, Promise.resolve(new Map()));
27
28
  const renders = await Promise.all(Array.from(clients.entries(), async ([ufgClient, targets]) => {
29
+ const accessibilityReportOptions = targets.map(target => target.settings.accessibilitySettings
30
+ ? (0, build_accessibility_report_options_1.buildAccessibilityReportOptions)({ accessibilityOptions: target.account.accessibilityOptions })
31
+ : undefined);
28
32
  const [renders, ...environments] = await Promise.all([
29
33
  ufgClient.startRenders({
30
- renders: targets.map(target => {
34
+ renders: targets.map((target, index) => {
31
35
  var _a;
32
36
  const { elementReferences: selectors } = (0, to_base_check_settings_1.toBaseCheckSettings)({ settings: target.settings });
33
37
  return {
@@ -41,6 +45,7 @@ function makeStartRenders({ core, logger: defaultLogger }) {
41
45
  environment: target.environment,
42
46
  uploadUrl: target.account.uploadUrl,
43
47
  stitchingServiceUrl: target.account.stitchingServiceUrl,
48
+ accessibilityReportOptions: accessibilityReportOptions[index],
44
49
  },
45
50
  };
46
51
  }),
@@ -54,12 +59,16 @@ function makeStartRenders({ core, logger: defaultLogger }) {
54
59
  return { ...environment, properties: target.environment.properties };
55
60
  }),
56
61
  ]);
57
- return renders.map((render, index) => ({
58
- render,
59
- settings: targets[index].settings,
60
- environment: environments[index],
61
- account: targets[index].account,
62
- }));
62
+ return renders.map((render, index) => {
63
+ var _a;
64
+ return ({
65
+ render,
66
+ settings: targets[index].settings,
67
+ environment: environments[index],
68
+ account: targets[index].account,
69
+ accessibilityReportLocation: (_a = accessibilityReportOptions[index]) === null || _a === void 0 ? void 0 : _a.uploadUrl,
70
+ });
71
+ });
63
72
  }));
64
73
  return renders.flat();
65
74
  };
@@ -26,10 +26,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.makeCheckAndClose = void 0;
27
27
  const driver_1 = require("@applitools/driver");
28
28
  const utils = __importStar(require("@applitools/utils"));
29
- function makeCheckAndClose({ type: defaultType = 'classic', eyes, target: defaultTarget, spec, logger: mainLogger, }) {
29
+ function makeCheckAndClose({ type: defaultType = 'classic', eyes, target: defaultTarget, environments: openEnvironments, spec, logger: mainLogger, }) {
30
30
  return async function checkAndClose({ type = defaultType, target = defaultTarget, settings = {}, config, logger = mainLogger, } = {}) {
31
31
  logger = logger.extend(mainLogger, { tags: [`check-and-close-${type}-${utils.general.shortid()}`] });
32
+ // Same environments precedence as in check.ts.
33
+ const explicitEnvironments = settings != null && 'environments' in settings;
32
34
  settings = { ...config === null || config === void 0 ? void 0 : config.screenshot, ...config === null || config === void 0 ? void 0 : config.check, ...config === null || config === void 0 ? void 0 : config.close, ...settings };
35
+ if (type === 'ufg' && (openEnvironments === null || openEnvironments === void 0 ? void 0 : openEnvironments.length) && !explicitEnvironments) {
36
+ ;
37
+ settings.environments = openEnvironments;
38
+ }
33
39
  const driver = (0, driver_1.isDriver)(target, spec)
34
40
  ? await (0, driver_1.makeDriver)({ spec, driver: target, reset: target === defaultTarget, logger })
35
41
  : null;
package/dist/check.js CHANGED
@@ -32,13 +32,20 @@ const defaults_1 = require("./automation/utils/defaults");
32
32
  const utils = __importStar(require("@applitools/utils"));
33
33
  const lang = __importStar(require("./lang"));
34
34
  const chalk_1 = __importDefault(require("chalk"));
35
- function makeCheck({ type: defaultType = 'classic', eyes, target: defaultTarget, spec, logger: mainLogger, }) {
35
+ function makeCheck({ type: defaultType = 'classic', eyes, target: defaultTarget, environments: openEnvironments, spec, logger: mainLogger, }) {
36
36
  let stepIndex = 0;
37
37
  return async function check({ type = defaultType, target = defaultTarget, settings, config, logger = mainLogger, } = {}) {
38
38
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
39
39
  var _t, _u, _v, _w, _x;
40
40
  logger = logger.extend(mainLogger, { tags: [`check-${type}-${utils.general.shortid()}`] });
41
+ // Open-time environments (possibly rewritten by the figma seam) win over the config's per-check
42
+ // re-send; environments passed explicitly to this call win over both.
43
+ const explicitEnvironments = settings != null && 'environments' in settings;
41
44
  settings = { ...config === null || config === void 0 ? void 0 : config.screenshot, ...config === null || config === void 0 ? void 0 : config.check, ...settings };
45
+ if (type === 'ufg' && (openEnvironments === null || openEnvironments === void 0 ? void 0 : openEnvironments.length) && !explicitEnvironments) {
46
+ ;
47
+ settings.environments = openEnvironments;
48
+ }
42
49
  (_a = settings.fully) !== null && _a !== void 0 ? _a : (settings.fully = !settings.region && (!settings.frames || settings.frames.length === 0));
43
50
  (_b = settings.waitBeforeCapture) !== null && _b !== void 0 ? _b : (settings.waitBeforeCapture = defaults_1.DEFAULT_WAIT_BEFORE_CAPTURE);
44
51
  (_c = settings.hideScrollbars) !== null && _c !== void 0 ? _c : (settings.hideScrollbars = true);
@@ -34,8 +34,23 @@ async function extractDefaultEnvironments({ driver, settings, }) {
34
34
  try {
35
35
  const driverEnvironment = await driver.getEnvironment();
36
36
  const viewport = await driver.getViewport();
37
- // scale layout viewport by visualViewport.scale to get visible CSS viewport (avoids double-scaling spec drivers that already return CSS pixels)
38
- environment.viewportSize = utils.geometry.scale(viewport.viewportSize, viewport.viewportScale);
37
+ // The reported viewport is part of the baseline key: it must stay identical to the historical
38
+ // derivation (`layout viewport × visualViewport.scale`) for every viewport already reported
39
+ // correctly (FLD-4359). The framework-reported CSS viewport may substitute only to correct
40
+ // sub-pixel rounding noise in that derivation (FLD-4814): integral on both axes, within < 1px
41
+ // per axis, and viewportScale <= 1 — the residue bound the backward-compatibility proof depends
42
+ // on. The value must come from getCssViewportSize(), never getViewportSize(), whose fallback is
43
+ // the unscaled layout viewport. Full derivation, proof and history: llm-docs/CLASSIC.md →
44
+ // Reported viewport.
45
+ const derivedViewportSize = utils.geometry.scale(viewport.viewportSize, viewport.viewportScale);
46
+ const cssViewportSize = await driver.getCssViewportSize();
47
+ const corroboratesDerivedSize = !!cssViewportSize &&
48
+ viewport.viewportScale <= 1 &&
49
+ Number.isInteger(cssViewportSize.width) &&
50
+ Number.isInteger(cssViewportSize.height) &&
51
+ Math.abs(cssViewportSize.width - derivedViewportSize.width) < 1 &&
52
+ Math.abs(cssViewportSize.height - derivedViewportSize.height) < 1;
53
+ environment.viewportSize = corroboratesDerivedSize ? cssViewportSize : derivedViewportSize;
39
54
  environment.environmentId = utils.general.guid();
40
55
  if (driverEnvironment.isEC)
41
56
  environment.ecSessionId = (_a = (await driver.getSessionId())) !== null && _a !== void 0 ? _a : undefined;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeFigmaReq = exports.FIGMA_API = void 0;
4
+ // The Figma REST transport: `@applitools/req` configured for Figma's rate limits.
5
+ const req_1 = require("@applitools/req");
6
+ exports.FIGMA_API = 'https://api.figma.com/v1';
7
+ // `limit` is what bounds the number of retries, not `retryTimeout`: Figma may answer `Retry-After: 0`, and
8
+ // a zero delay never advances an elapsed-time budget.
9
+ const RETRY_LIMIT = 5;
10
+ // Only used when Figma omits `Retry-After`.
11
+ const RETRY_BACKOFF_MS = [1000, 2000, 5000, 10000];
12
+ function makeFigmaReq({ proxy, logger, maxRateLimitWaitSeconds = 60 } = {}) {
13
+ const maxWaitMs = maxRateLimitWaitSeconds * 1000;
14
+ return (0, req_1.makeReq)({
15
+ proxy,
16
+ retry: {
17
+ limit: RETRY_LIMIT,
18
+ timeout: RETRY_BACKOFF_MS,
19
+ /**
20
+ * Deciding here rather than with `statuses: [429]` is what enforces the wait budget. `req` sleeps
21
+ * `Retry-After` in full and only compares `retryTimeout` against time already ELAPSED, so accepting a
22
+ * retry commits to the whole delay: an hour, a day or a week each block for their full duration.
23
+ * (Beyond ~24.8 days it overflows `setTimeout` and fires almost immediately — wrong in the other
24
+ * direction.) Refusing the retry surfaces the 429 to the caller instead.
25
+ */
26
+ validate: ({ response }) => {
27
+ if ((response === null || response === void 0 ? void 0 : response.status) !== 429)
28
+ return false;
29
+ const retryAfterMs = Number(response.headers.get('Retry-After')) * 1000;
30
+ // Unparseable (e.g. an HTTP-date) reads as NaN: retry on the backoff schedule rather than trust it.
31
+ if (!Number.isFinite(retryAfterMs))
32
+ return true;
33
+ if (retryAfterMs > maxWaitMs) {
34
+ logger === null || logger === void 0 ? void 0 : logger.log(`Figma rate limit — Retry-After of ${retryAfterMs / 1000}s exceeds the ${maxRateLimitWaitSeconds}s budget; giving up.`);
35
+ return false;
36
+ }
37
+ // Logged here because `beforeRetry` fires only after the sleep, leaving a long wait unexplained.
38
+ if (retryAfterMs > 0)
39
+ logger === null || logger === void 0 ? void 0 : logger.log(`Figma rate limit — waiting ${retryAfterMs / 1000}s before retry...`);
40
+ return true;
41
+ },
42
+ },
43
+ retryTimeout: maxWaitMs,
44
+ });
45
+ }
46
+ exports.makeFigmaReq = makeFigmaReq;
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.fetchFigmaImages = exports.fetchFigmaMetadata = exports.figmaImageCacheFileName = exports.figmaNodesCacheFileName = void 0;
27
+ // Figma REST layer: batched /nodes + /images calls and their on-disk caches. Rate limiting, proxying and
28
+ // timeouts belong to the `req` instance (./figma-req).
29
+ // All metadata flows through fetchFigmaFileNodes so /nodes is fetched at most once per file.
30
+ const crypto = __importStar(require("crypto"));
31
+ const fs = __importStar(require("fs"));
32
+ const os = __importStar(require("os"));
33
+ const path = __importStar(require("path"));
34
+ const buffer_1 = require("buffer");
35
+ const utils_1 = require("../utils");
36
+ const figma_req_1 = require("./figma-req");
37
+ const DEFAULT_NODES_CACHE_TTL_SECONDS = 120;
38
+ /**
39
+ * Cache names are hashed because Figma node ids are not filename-safe — an instance node id looks like
40
+ * `I1:2;3:4`, and `:` / `;` are illegal on Windows.
41
+ *
42
+ * `test/fixtures/figma-cache/seed.mjs` mirrors both functions — keep them in sync.
43
+ */
44
+ function figmaNodesCacheFileName(fileId, uniqueNodeIds) {
45
+ const hash = crypto
46
+ .createHash('sha1')
47
+ .update(`${fileId}:${[...uniqueNodeIds].sort().join(',')}`)
48
+ .digest('hex');
49
+ return `figma-nodes-${hash}.json`;
50
+ }
51
+ exports.figmaNodesCacheFileName = figmaNodesCacheFileName;
52
+ function figmaImageCacheFileName(fileId, nodeId, lastModifiedUnix) {
53
+ const hash = crypto.createHash('sha1').update(`${fileId}:${nodeId}:${lastModifiedUnix}`).digest('hex');
54
+ return `figma-image-${hash}.png`;
55
+ }
56
+ exports.figmaImageCacheFileName = figmaImageCacheFileName;
57
+ async function figmaGet(req, endpoint, accessToken) {
58
+ const response = await req(`${figma_req_1.FIGMA_API}${endpoint}`, {
59
+ headers: { 'X-Figma-Token': accessToken },
60
+ });
61
+ if (!response.ok) {
62
+ const body = await response.text().catch(() => '');
63
+ throw new Error(`Figma API error ${response.status}${body ? `: ${body}` : ''}`);
64
+ }
65
+ return response.json();
66
+ }
67
+ // Grouping by file is what keeps API usage proportional to files touched, not URLs requested.
68
+ function groupByFile(urls) {
69
+ const byFile = new Map();
70
+ urls.forEach((url, urlIndex) => {
71
+ const { fileId, nodeId } = (0, utils_1.parseFigmaNodeUrl)(url);
72
+ if (!byFile.has(fileId))
73
+ byFile.set(fileId, []);
74
+ byFile.get(fileId).push({ nodeId, urlIndex });
75
+ });
76
+ return byFile;
77
+ }
78
+ // The single source of node metadata — both entry points route through here, so /nodes is never fetched twice.
79
+ async function fetchFigmaFileNodes(urls, accessToken, opts) {
80
+ const resolved = [];
81
+ for (const [fileId, nodes] of groupByFile(urls)) {
82
+ const uniqueNodeIds = [...new Set(nodes.map(n => n.nodeId))].sort();
83
+ const nodesFilePath = path.join(opts.tempPath, figmaNodesCacheFileName(fileId, uniqueNodeIds));
84
+ let nodesResp;
85
+ if (fs.existsSync(nodesFilePath)) {
86
+ const stat = fs.statSync(nodesFilePath);
87
+ const ageSeconds = (Date.now() - stat.mtimeMs) / 1000;
88
+ if (opts.skipApi || ageSeconds < opts.ttlSeconds) {
89
+ nodesResp = JSON.parse(fs.readFileSync(nodesFilePath, 'utf8'));
90
+ }
91
+ }
92
+ if (!nodesResp) {
93
+ if (opts.skipApi) {
94
+ throw new Error(`APPLITOOLS_FIGMA_SKIP_API is set but no nodes cache found at ${nodesFilePath}`);
95
+ }
96
+ const ids = uniqueNodeIds.map(id => encodeURIComponent(id)).join(',');
97
+ nodesResp = await figmaGet(opts.req, `/files/${fileId}/nodes?ids=${ids}`, accessToken);
98
+ fs.writeFileSync(nodesFilePath, JSON.stringify(nodesResp));
99
+ }
100
+ resolved.push({ fileId, nodesResp, nodes });
101
+ }
102
+ return resolved;
103
+ }
104
+ function metadataFromNodesResp(nodesResp, fileId, nodeId) {
105
+ var _a, _b, _c;
106
+ const nodeData = (_a = nodesResp.nodes) === null || _a === void 0 ? void 0 : _a[nodeId];
107
+ if (!nodeData) {
108
+ throw new Error(`Node "${nodeId}" not found in file "${fileId}"`);
109
+ }
110
+ const doc = nodeData.document;
111
+ const box = (_b = doc.absoluteBoundingBox) !== null && _b !== void 0 ? _b : doc.absoluteRenderBounds;
112
+ if (!box) {
113
+ throw new Error(`Node "${nodeId}" has no bounding box`);
114
+ }
115
+ return {
116
+ name: doc.name,
117
+ type: doc.type,
118
+ width: Math.round(box.width),
119
+ height: Math.round(box.height),
120
+ revision: String((_c = nodesResp.version) !== null && _c !== void 0 ? _c : 'unknown'),
121
+ lastModifiedAt: nodesResp.lastModified,
122
+ };
123
+ }
124
+ function resolveRestOptions(opts) {
125
+ var _a, _b, _c, _d;
126
+ return {
127
+ tempPath: (_a = opts === null || opts === void 0 ? void 0 : opts.tempPath) !== null && _a !== void 0 ? _a : os.tmpdir(),
128
+ ttlSeconds: (_b = opts === null || opts === void 0 ? void 0 : opts.nodesCacheTtlSeconds) !== null && _b !== void 0 ? _b : DEFAULT_NODES_CACHE_TTL_SECONDS,
129
+ skipApi: (_c = opts === null || opts === void 0 ? void 0 : opts.skipApi) !== null && _c !== void 0 ? _c : false,
130
+ req: (_d = opts === null || opts === void 0 ? void 0 : opts.req) !== null && _d !== void 0 ? _d : (0, figma_req_1.makeFigmaReq)({
131
+ proxy: opts === null || opts === void 0 ? void 0 : opts.proxy,
132
+ logger: opts === null || opts === void 0 ? void 0 : opts.logger,
133
+ maxRateLimitWaitSeconds: opts === null || opts === void 0 ? void 0 : opts.maxRateLimitWaitSeconds,
134
+ }),
135
+ };
136
+ }
137
+ // No /images render or PNG download — `test-baseline` mode sizes the viewport from metadata alone.
138
+ async function fetchFigmaMetadata(urls, accessToken, opts) {
139
+ const { tempPath, ttlSeconds, skipApi, req } = resolveRestOptions(opts);
140
+ const fileGroups = await fetchFigmaFileNodes(urls, accessToken, { tempPath, ttlSeconds, skipApi, req });
141
+ const results = new Array(urls.length);
142
+ for (const { fileId, nodesResp, nodes } of fileGroups) {
143
+ for (const { nodeId, urlIndex } of nodes) {
144
+ results[urlIndex] = metadataFromNodesResp(nodesResp, fileId, nodeId);
145
+ }
146
+ }
147
+ return results;
148
+ }
149
+ exports.fetchFigmaMetadata = fetchFigmaMetadata;
150
+ async function fetchFigmaImages(urls, accessToken, opts) {
151
+ const { tempPath, skipApi, req } = resolveRestOptions(opts);
152
+ // Pass `req` through so an injected stub or proxy applies to the /nodes leg too.
153
+ const metadataList = await fetchFigmaMetadata(urls, accessToken, { ...opts, req });
154
+ const results = new Array(urls.length);
155
+ for (const [fileId, nodes] of groupByFile(urls)) {
156
+ const uniqueNodeIds = [...new Set(nodes.map(n => n.nodeId))].sort();
157
+ // lastModified is file-level, so any node of this file carries it. Absent ⇒ NaN, which would poison
158
+ // the cache key.
159
+ const lastModifiedMs = new Date(metadataList[nodes[0].urlIndex].lastModifiedAt).getTime();
160
+ const lastModifiedUnix = Number.isFinite(lastModifiedMs) ? Math.floor(lastModifiedMs / 1000) : 0;
161
+ const imagePathFor = (nodeId) => path.join(tempPath, figmaImageCacheFileName(fileId, nodeId, lastModifiedUnix));
162
+ const missNodeIds = uniqueNodeIds.filter(nodeId => !fs.existsSync(imagePathFor(nodeId)));
163
+ if (missNodeIds.length > 0) {
164
+ if (skipApi) {
165
+ throw new Error(`APPLITOOLS_FIGMA_SKIP_API is set but no image cache found for node ${missNodeIds[0]} in file ${fileId}`);
166
+ }
167
+ const ids = missNodeIds.map(id => encodeURIComponent(id)).join(',');
168
+ const imagesResp = await figmaGet(req, `/images/${fileId}?ids=${ids}&format=png&scale=1`, accessToken);
169
+ await Promise.all(missNodeIds.map(async (nodeId) => {
170
+ var _a;
171
+ const imageUrl = (_a = imagesResp.images) === null || _a === void 0 ? void 0 : _a[nodeId];
172
+ if (!imageUrl) {
173
+ throw new Error(`No image URL returned for node "${nodeId}"`);
174
+ }
175
+ // The CDN URL is pre-signed and carries no Figma token — no auth header here.
176
+ const imageResponse = await req(imageUrl);
177
+ if (!imageResponse.ok) {
178
+ throw new Error(`Failed to download Figma image: HTTP ${imageResponse.status}`);
179
+ }
180
+ fs.writeFileSync(imagePathFor(nodeId), buffer_1.Buffer.from(await imageResponse.arrayBuffer()));
181
+ }));
182
+ }
183
+ for (const { nodeId, urlIndex } of nodes) {
184
+ results[urlIndex] = { imagePath: imagePathFor(nodeId), metadata: metadataList[urlIndex] };
185
+ }
186
+ }
187
+ return results;
188
+ }
189
+ exports.fetchFigmaImages = fetchFigmaImages;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ // The Figma REST client: transport configuration (./figma-req) plus the batched /nodes + /images calls
18
+ // and their disk caches (./figma-rest). Takes logger, proxy, token, skipApi and cache settings as inputs —
19
+ // it reads no environment of its own.
20
+ __exportStar(require("./figma-req"), exports);
21
+ __exportStar(require("./figma-rest"), exports);