@applitools/core 4.68.0 → 4.69.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,209 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.69.0](https://github.com/Applitools-Dev/sdk/compare/js/core@4.68.1...js/core@4.69.0) (2026-08-19)
4
+
5
+
6
+ ### Features
7
+
8
+ * label and tag figma logs, warn that the feature is experimental | AD-15681 ([#4108](https://github.com/Applitools-Dev/sdk/issues/4108)) ([5b20bf3](https://github.com/Applitools-Dev/sdk/commit/5b20bf327a857f09b9d3b1d55d59976390fae9fb))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * fail loudly when the Figma rate limit blocks further requests | AD-15531 ([#4065](https://github.com/Applitools-Dev/sdk/issues/4065)) ([9240e3e](https://github.com/Applitools-Dev/sdk/commit/9240e3ea6bed5225e8c9802cfb4669099968fc11))
14
+ * report a stalled lazyLoad pass instead of success under a scroll lock | FLD-3965 ([#4101](https://github.com/Applitools-Dev/sdk/issues/4101)) ([6244790](https://github.com/Applitools-Dev/sdk/commit/62447903464d27027f169d6b058d20e3e943ffda))
15
+
16
+
17
+ ### Code Refactoring
18
+
19
+ * make the figma logger and req explicit inputs | AD-15681 ([#4109](https://github.com/Applitools-Dev/sdk/issues/4109)) ([2a29778](https://github.com/Applitools-Dev/sdk/commit/2a2977865f1f26b0b55f7ab004c8ff737ed2573c))
20
+
21
+
22
+ ### Dependencies
23
+
24
+ * @applitools/dom-snapshot bumped to 4.18.0
25
+ #### Features
26
+
27
+ * report canvases captured before they are drawn | FLD-3965 ([#4097](https://github.com/Applitools-Dev/sdk/issues/4097)) ([48b92ec](https://github.com/Applitools-Dev/sdk/commit/48b92ecba0804ad77333a6b4c799435d998f1418))
28
+
29
+
30
+
31
+ * @applitools/snippets bumped to 2.9.7
32
+ #### Bug Fixes
33
+
34
+ * report a stalled lazyLoad pass instead of success under a scroll lock | FLD-3965 ([#4101](https://github.com/Applitools-Dev/sdk/issues/4101)) ([6244790](https://github.com/Applitools-Dev/sdk/commit/62447903464d27027f169d6b058d20e3e943ffda))
35
+ * @applitools/nml-client bumped to 1.12.0
36
+ #### Features
37
+
38
+ * seat region-check DOM via NML crop targetRegion | AD-14690 ([#4054](https://github.com/Applitools-Dev/sdk/issues/4054)) ([d442ff3](https://github.com/Applitools-Dev/sdk/commit/d442ff336b66144b55102d04b74175305b05169e))
39
+
40
+
41
+
42
+ * @applitools/driver bumped to 1.26.9
43
+
44
+ * @applitools/spec-driver-webdriver bumped to 1.6.9
45
+
46
+ * @applitools/spec-driver-selenium bumped to 1.8.9
47
+
48
+ * @applitools/spec-driver-playwright bumped to 1.9.9
49
+
50
+ * @applitools/spec-driver-puppeteer bumped to 1.8.9
51
+
52
+ * @applitools/screenshoter bumped to 3.12.29
53
+
54
+ * @applitools/ec-client bumped to 1.12.43
55
+
56
+
57
+ ## [4.68.1](https://github.com/Applitools-Dev/sdk/compare/js/core@4.68.0...js/core@4.68.1) (2026-08-10)
58
+
59
+
60
+ ### Bug Fixes
61
+
62
+ * 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))
63
+ * 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))
64
+ * 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))
65
+
66
+
67
+ ### Dependencies
68
+
69
+ * @applitools/utils bumped to 1.15.2
70
+ #### Bug Fixes
71
+
72
+ * 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))
73
+ * @applitools/logger bumped to 2.3.1
74
+ #### Bug Fixes
75
+
76
+ * 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))
77
+
78
+
79
+
80
+ * @applitools/dom-shared bumped to 1.2.3
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
+ * @applitools/dom-snapshot bumped to 4.17.9
85
+ #### Bug Fixes
86
+
87
+ * 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))
88
+
89
+
90
+
91
+ * @applitools/socket bumped to 1.3.16
92
+ #### Bug Fixes
93
+
94
+ * 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))
95
+
96
+
97
+
98
+ * @applitools/req bumped to 1.11.3
99
+ #### Bug Fixes
100
+
101
+ * 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))
102
+
103
+
104
+
105
+ * @applitools/image bumped to 1.3.1
106
+ #### Bug Fixes
107
+
108
+ * 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))
109
+
110
+
111
+
112
+ * @applitools/snippets bumped to 2.9.6
113
+ #### Bug Fixes
114
+
115
+ * 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))
116
+
117
+
118
+
119
+ * @applitools/dom-capture bumped to 11.8.5
120
+ #### Bug Fixes
121
+
122
+ * 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))
123
+
124
+
125
+
126
+ * @applitools/driver bumped to 1.26.8
127
+ #### Bug Fixes
128
+
129
+ * 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))
130
+ * 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))
131
+
132
+
133
+
134
+ * @applitools/spec-driver-webdriver bumped to 1.6.8
135
+ #### Bug Fixes
136
+
137
+ * 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))
138
+
139
+
140
+
141
+ * @applitools/spec-driver-selenium bumped to 1.8.8
142
+ #### Bug Fixes
143
+
144
+ * 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))
145
+
146
+
147
+
148
+ * @applitools/spec-driver-playwright bumped to 1.9.8
149
+ #### Bug Fixes
150
+
151
+ * 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))
152
+
153
+
154
+
155
+ * @applitools/spec-driver-puppeteer bumped to 1.8.8
156
+ #### Bug Fixes
157
+
158
+ * 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))
159
+
160
+
161
+
162
+ * @applitools/screenshoter bumped to 3.12.28
163
+ #### Bug Fixes
164
+
165
+ * 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))
166
+
167
+
168
+
169
+ * @applitools/nml-client bumped to 1.11.40
170
+ #### Bug Fixes
171
+
172
+ * 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))
173
+
174
+
175
+
176
+ * @applitools/tunnel-client bumped to 1.12.3
177
+ #### Bug Fixes
178
+
179
+ * 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))
180
+
181
+
182
+
183
+ * @applitools/ufg-client bumped to 1.23.1
184
+ #### Bug Fixes
185
+
186
+ * 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))
187
+
188
+
189
+
190
+ * @applitools/core-base bumped to 1.38.1
191
+ #### Bug Fixes
192
+
193
+ * 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))
194
+
195
+
196
+
197
+ * @applitools/ec-client bumped to 1.12.42
198
+ #### Bug Fixes
199
+
200
+ * 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))
201
+
202
+
203
+
204
+ * @applitools/test-server bumped to 1.4.7
205
+
206
+
3
207
  ## [4.68.0](https://github.com/Applitools-Dev/sdk/compare/js/core@4.67.2...js/core@4.68.0) (2026-08-05)
4
208
 
5
209
 
@@ -6,7 +6,7 @@ async function waitForLazyLoad({ context, settings = {}, logger, }) {
6
6
  var _a;
7
7
  logger.log('Running scrolling sequence to lazy load a view content');
8
8
  const { scrollLength = 300, waitingTime = 2000, maxAmountToScroll = 15000 } = settings;
9
- await context.executePoll(lazyLoad, {
9
+ const result = await context.executePoll(lazyLoad, {
10
10
  main: [
11
11
  await context.getScrollingElement(),
12
12
  {
@@ -19,5 +19,22 @@ async function waitForLazyLoad({ context, settings = {}, logger, }) {
19
19
  executionTimeout: 300000 /* 5min */,
20
20
  pollTimeout: (_a = settings.pollTimeout) !== null && _a !== void 0 ? _a : waitingTime,
21
21
  });
22
+ try {
23
+ const steps = Array.isArray(result) ? result : undefined;
24
+ const last = steps === null || steps === void 0 ? void 0 : steps[steps.length - 1];
25
+ if ((last === null || last === void 0 ? void 0 : last.reason) === 'stalled' || (last === null || last === void 0 ? void 0 : last.reason) === 'cappedAt') {
26
+ const coverage = `at ${last.maxScrollTop}px of ${last.maxScrollHeight}px`;
27
+ logger.console.log(last.reason === 'stalled'
28
+ ? `Lazy loading stopped early: the page stopped scrolling ${coverage}, ` +
29
+ 'which usually means something locked the page (for example a modal) while the content was being ' +
30
+ 'scrolled through. Content below that point may be missing from the captured page.'
31
+ : `Lazy loading stopped early: the scrolling attempt limit was reached ${coverage}. ` +
32
+ 'Content below that point may be missing from the captured page.');
33
+ logger.log('lazy load ended early', last);
34
+ }
35
+ }
36
+ catch (error) {
37
+ logger.log('Failed to inspect the lazy load result', error);
38
+ }
22
39
  }
23
40
  exports.waitForLazyLoad = waitForLazyLoad;
@@ -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;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.makeFigmaReq = exports.FIGMA_API = void 0;
3
+ exports.makeFigmaReq = exports.figmaRateLimitMessage = exports.parseRetryAfterSeconds = exports.DEFAULT_MAX_RATE_LIMIT_WAIT_SECONDS = exports.FIGMA_API = void 0;
4
4
  // The Figma REST transport: `@applitools/req` configured for Figma's rate limits.
5
5
  const req_1 = require("@applitools/req");
6
6
  exports.FIGMA_API = 'https://api.figma.com/v1';
@@ -9,7 +9,42 @@ exports.FIGMA_API = 'https://api.figma.com/v1';
9
9
  const RETRY_LIMIT = 5;
10
10
  // Only used when Figma omits `Retry-After`.
11
11
  const RETRY_BACKOFF_MS = [1000, 2000, 5000, 10000];
12
- function makeFigmaReq({ proxy, logger, maxRateLimitWaitSeconds = 60 } = {}) {
12
+ exports.DEFAULT_MAX_RATE_LIMIT_WAIT_SECONDS = 60;
13
+ /** Undefined when Figma sent no `Retry-After`, or one this doesn't parse (e.g. an HTTP-date). */
14
+ function parseRetryAfterSeconds(headers) {
15
+ const seconds = Number(headers.get('Retry-After'));
16
+ return headers.has('Retry-After') && Number.isFinite(seconds) ? seconds : undefined;
17
+ }
18
+ exports.parseRetryAfterSeconds = parseRetryAfterSeconds;
19
+ /**
20
+ * The single wording for "Figma throttled us and we stopped trying", shared by the log line here and by
21
+ * the errors `figma-rest` throws — a user who sees only one of the two still gets the whole cause.
22
+ */
23
+ function figmaRateLimitMessage(ending, maxRateLimitWaitSeconds) {
24
+ const budget = `${maxRateLimitWaitSeconds}s wait budget (figmaOptions.maxRateLimitWaitSeconds)`;
25
+ let cause;
26
+ switch (ending.reason) {
27
+ case 'retry-after-over-budget':
28
+ cause =
29
+ `Figma's Retry-After asked for ${ending.retryAfterSeconds}s, over the ${budget}, so no retry was ` +
30
+ `attempted. Raising that budget only helps for short waits — a Retry-After of hours or days cannot ` +
31
+ `be waited out.`;
32
+ break;
33
+ case 'wait-budget-exhausted':
34
+ cause = `retrying consumed the ${budget}. A larger budget can help when Figma's Retry-After is short.`;
35
+ break;
36
+ case 'attempts-exhausted':
37
+ cause =
38
+ `Figma was still throttling after the retry limit of ${RETRY_LIMIT} attempts, which is not ` +
39
+ `configurable — raising the ${budget} does not change it.`;
40
+ break;
41
+ }
42
+ return (`Figma API rate limit (HTTP 429): ${cause} Rerun against a warm on-disk Figma cache ` +
43
+ `(figmaOptions.tempPath / APPLITOOLS_FIGMA_TEMP_PATH — an earlier run must have populated it, and ` +
44
+ `figmaOptions.nodesCacheTtlSeconds must still cover it), or use a token that is not throttled.`);
45
+ }
46
+ exports.figmaRateLimitMessage = figmaRateLimitMessage;
47
+ function makeFigmaReq({ proxy, logger, maxRateLimitWaitSeconds = exports.DEFAULT_MAX_RATE_LIMIT_WAIT_SECONDS, } = {}) {
13
48
  const maxWaitMs = maxRateLimitWaitSeconds * 1000;
14
49
  return (0, req_1.makeReq)({
15
50
  proxy,
@@ -26,17 +61,19 @@ function makeFigmaReq({ proxy, logger, maxRateLimitWaitSeconds = 60 } = {}) {
26
61
  validate: ({ response }) => {
27
62
  if ((response === null || response === void 0 ? void 0 : response.status) !== 429)
28
63
  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))
64
+ // No duration to weigh against the budget, so retry: on the backoff schedule when Figma sent no
65
+ // `Retry-After` at all, but immediately when it sent one this can't parse — req reads the header
66
+ // itself and turns an unparseable value into a NaN delay. Tracked in AD-15577.
67
+ const retryAfterSeconds = parseRetryAfterSeconds(response.headers);
68
+ if (retryAfterSeconds === undefined)
32
69
  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.`);
70
+ if (retryAfterSeconds > maxRateLimitWaitSeconds) {
71
+ logger === null || logger === void 0 ? void 0 : logger.error(figmaRateLimitMessage({ reason: 'retry-after-over-budget', retryAfterSeconds }, maxRateLimitWaitSeconds));
35
72
  return false;
36
73
  }
37
74
  // 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...`);
75
+ if (retryAfterSeconds > 0)
76
+ logger === null || logger === void 0 ? void 0 : logger.log(`Figma rate limit — waiting ${retryAfterSeconds}s before retry...`);
40
77
  return true;
41
78
  },
42
79
  },
@@ -32,6 +32,7 @@ const fs = __importStar(require("fs"));
32
32
  const os = __importStar(require("os"));
33
33
  const path = __importStar(require("path"));
34
34
  const buffer_1 = require("buffer");
35
+ const req_1 = require("@applitools/req");
35
36
  const utils_1 = require("../utils");
36
37
  const figma_req_1 = require("./figma-req");
37
38
  const DEFAULT_NODES_CACHE_TTL_SECONDS = 120;
@@ -54,8 +55,34 @@ function figmaImageCacheFileName(fileId, nodeId, lastModifiedUnix) {
54
55
  return `figma-image-${hash}.png`;
55
56
  }
56
57
  exports.figmaImageCacheFileName = figmaImageCacheFileName;
57
- async function figmaGet(req, endpoint, accessToken) {
58
- const response = await req(`${figma_req_1.FIGMA_API}${endpoint}`, {
58
+ /**
59
+ * Every `req` call in this file goes through here — the Figma API legs and the CDN image download alike.
60
+ * A terminal rate limit surfaces two ways, neither of which carries a diagnosis of its own, and each
61
+ * caller's own status message would bury it.
62
+ */
63
+ async function figmaFetch(req, url, maxRateLimitWaitSeconds, init = {}) {
64
+ const response = await req(url, init).catch(error => {
65
+ // Waits that each fit the budget but together outlast it end here: req abandons the retry loop
66
+ // itself, so no 429 response is left to read. `name` rather than `instanceof` alone so an injected
67
+ // `opts.req` or a duplicated req copy still matches. `cause` keeps req's own error (and its
68
+ // `RETRY_DURATION_EXCEEDED` code) reachable — `Error`'s two-argument form is not typed at es2019.
69
+ if (error instanceof req_1.RetryTimeoutError || (error === null || error === void 0 ? void 0 : error.name) === 'RetryTimeoutError') {
70
+ throw Object.assign(new Error((0, figma_req_1.figmaRateLimitMessage)({ reason: 'wait-budget-exhausted' }, maxRateLimitWaitSeconds)), { cause: error });
71
+ }
72
+ throw error;
73
+ });
74
+ if (response.status === 429) {
75
+ // The retry policy in ./figma-req refuses an over-budget `Retry-After` outright and otherwise stops at
76
+ // the attempt limit. Which of the two happened decides what the user can do about it.
77
+ const retryAfterSeconds = (0, figma_req_1.parseRetryAfterSeconds)(response.headers);
78
+ throw new Error((0, figma_req_1.figmaRateLimitMessage)(retryAfterSeconds !== undefined && retryAfterSeconds > maxRateLimitWaitSeconds
79
+ ? { reason: 'retry-after-over-budget', retryAfterSeconds }
80
+ : { reason: 'attempts-exhausted' }, maxRateLimitWaitSeconds));
81
+ }
82
+ return response;
83
+ }
84
+ async function figmaGet(req, endpoint, accessToken, maxRateLimitWaitSeconds) {
85
+ const response = await figmaFetch(req, `${figma_req_1.FIGMA_API}${endpoint}`, maxRateLimitWaitSeconds, {
59
86
  headers: { 'X-Figma-Token': accessToken },
60
87
  });
61
88
  if (!response.ok) {
@@ -94,7 +121,7 @@ async function fetchFigmaFileNodes(urls, accessToken, opts) {
94
121
  throw new Error(`APPLITOOLS_FIGMA_SKIP_API is set but no nodes cache found at ${nodesFilePath}`);
95
122
  }
96
123
  const ids = uniqueNodeIds.map(id => encodeURIComponent(id)).join(',');
97
- nodesResp = await figmaGet(opts.req, `/files/${fileId}/nodes?ids=${ids}`, accessToken);
124
+ nodesResp = await figmaGet(opts.req, `/files/${fileId}/nodes?ids=${ids}`, accessToken, opts.maxRateLimitWaitSeconds);
98
125
  fs.writeFileSync(nodesFilePath, JSON.stringify(nodesResp));
99
126
  }
100
127
  resolved.push({ fileId, nodesResp, nodes });
@@ -124,20 +151,23 @@ function metadataFromNodesResp(nodesResp, fileId, nodeId) {
124
151
  function resolveRestOptions(opts) {
125
152
  var _a, _b, _c, _d;
126
153
  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
- }),
154
+ tempPath: (_a = opts.tempPath) !== null && _a !== void 0 ? _a : os.tmpdir(),
155
+ ttlSeconds: (_b = opts.nodesCacheTtlSeconds) !== null && _b !== void 0 ? _b : DEFAULT_NODES_CACHE_TTL_SECONDS,
156
+ skipApi: (_c = opts.skipApi) !== null && _c !== void 0 ? _c : false,
157
+ maxRateLimitWaitSeconds: (_d = opts.maxRateLimitWaitSeconds) !== null && _d !== void 0 ? _d : figma_req_1.DEFAULT_MAX_RATE_LIMIT_WAIT_SECONDS,
158
+ req: opts.req,
135
159
  };
136
160
  }
137
161
  // No /images render or PNG download — `test-baseline` mode sizes the viewport from metadata alone.
138
162
  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 });
163
+ const { tempPath, ttlSeconds, skipApi, req, maxRateLimitWaitSeconds } = resolveRestOptions(opts);
164
+ const fileGroups = await fetchFigmaFileNodes(urls, accessToken, {
165
+ tempPath,
166
+ ttlSeconds,
167
+ skipApi,
168
+ req,
169
+ maxRateLimitWaitSeconds,
170
+ });
141
171
  const results = new Array(urls.length);
142
172
  for (const { fileId, nodesResp, nodes } of fileGroups) {
143
173
  for (const { nodeId, urlIndex } of nodes) {
@@ -148,9 +178,8 @@ async function fetchFigmaMetadata(urls, accessToken, opts) {
148
178
  }
149
179
  exports.fetchFigmaMetadata = fetchFigmaMetadata;
150
180
  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 });
181
+ const { tempPath, skipApi, req, maxRateLimitWaitSeconds } = resolveRestOptions(opts);
182
+ const metadataList = await fetchFigmaMetadata(urls, accessToken, opts);
154
183
  const results = new Array(urls.length);
155
184
  for (const [fileId, nodes] of groupByFile(urls)) {
156
185
  const uniqueNodeIds = [...new Set(nodes.map(n => n.nodeId))].sort();
@@ -165,7 +194,7 @@ async function fetchFigmaImages(urls, accessToken, opts) {
165
194
  throw new Error(`APPLITOOLS_FIGMA_SKIP_API is set but no image cache found for node ${missNodeIds[0]} in file ${fileId}`);
166
195
  }
167
196
  const ids = missNodeIds.map(id => encodeURIComponent(id)).join(',');
168
- const imagesResp = await figmaGet(req, `/images/${fileId}?ids=${ids}&format=png&scale=1`, accessToken);
197
+ const imagesResp = await figmaGet(req, `/images/${fileId}?ids=${ids}&format=png&scale=1`, accessToken, maxRateLimitWaitSeconds);
169
198
  await Promise.all(missNodeIds.map(async (nodeId) => {
170
199
  var _a;
171
200
  const imageUrl = (_a = imagesResp.images) === null || _a === void 0 ? void 0 : _a[nodeId];
@@ -173,7 +202,7 @@ async function fetchFigmaImages(urls, accessToken, opts) {
173
202
  throw new Error(`No image URL returned for node "${nodeId}"`);
174
203
  }
175
204
  // The CDN URL is pre-signed and carries no Figma token — no auth header here.
176
- const imageResponse = await req(imageUrl);
205
+ const imageResponse = await figmaFetch(req, imageUrl, maxRateLimitWaitSeconds);
177
206
  if (!imageResponse.ok) {
178
207
  throw new Error(`Failed to download Figma image: HTTP ${imageResponse.status}`);
179
208
  }
@@ -15,7 +15,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
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.
18
+ // and their disk caches (./figma-rest). Takes the `req` instance, token, skipApi and cache settings as
19
+ // inputs — it reads no environment of its own.
20
20
  __exportStar(require("./figma-req"), exports);
21
21
  __exportStar(require("./figma-rest"), exports);
@@ -22,6 +22,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
25
28
  Object.defineProperty(exports, "__esModule", { value: true });
26
29
  exports.setFigmaBaseline = exports.setFigmaBaselineFlow = void 0;
27
30
  // Figma baseline resolver (AD-14881): orchestration only, operating directly on core's OpenSettings
@@ -34,7 +37,18 @@ const target_config_1 = require("./target-config");
34
37
  const client_1 = require("./client");
35
38
  const figma_images_1 = require("./figma-images");
36
39
  const run_figma_check_1 = require("./run-figma-check");
37
- async function setFigmaBaselineFlow(figmaUrls, originalConfig, deps, mode = 'auto-baseline', viewportOpts = {}, logger = (0, logger_1.makeLogger)()) {
40
+ const lang = __importStar(require("../lang"));
41
+ const chalk_1 = __importDefault(require("chalk"));
42
+ let experimentalWarned = false;
43
+ function warnExperimental(logger) {
44
+ logger.warn(lang.figmaExperimentalWarning);
45
+ // The console copy is once per process — a storybook run resolves a design per story.
46
+ if (experimentalWarned)
47
+ return;
48
+ experimentalWarned = true;
49
+ logger.console.warn(chalk_1.default.yellow(lang.figmaExperimentalWarning));
50
+ }
51
+ async function setFigmaBaselineFlow(figmaUrls, originalConfig, deps, logger, mode = 'auto-baseline', viewportOpts = {}) {
38
52
  var _a, _b;
39
53
  const maxViewportHeight = (_a = viewportOpts.maxViewportHeight) !== null && _a !== void 0 ? _a : target_config_1.DEFAULT_MAX_VIEWPORT_HEIGHT;
40
54
  const minViewportSize = (_b = viewportOpts.minViewportSize) !== null && _b !== void 0 ? _b : null;
@@ -105,7 +119,7 @@ async function setFigmaBaselineFlow(figmaUrls, originalConfig, deps, mode = 'aut
105
119
  batch: originalConfig.batch,
106
120
  appName: originalConfig.appName,
107
121
  testName: originalConfig.testName,
108
- environment: { hostingApp: 'Figma', os: '', viewportSize: figmaViewport },
122
+ environment: { hostingApp: 'Figma', viewportSize: figmaViewport },
109
123
  properties: (0, utils_1.figmaMetadataToProperties)(realResults[0].metadata, mode),
110
124
  baselineEnvName: figmaEnvName,
111
125
  saveNewTests: true,
@@ -124,9 +138,8 @@ async function setFigmaBaselineFlow(figmaUrls, originalConfig, deps, mode = 'aut
124
138
  return (0, target_config_1.buildTargetTestConfig)(originalConfig, realResults[0].metadata, figmaViewport, { figmaEnvName, useLatestEnvBaseline: mode === 'auto-baseline' }, mode);
125
139
  }
126
140
  exports.setFigmaBaselineFlow = setFigmaBaselineFlow;
127
- async function setFigmaBaseline(configuration, figmaBaselines, opts) {
128
- var _a;
129
- const logger = (_a = opts === null || opts === void 0 ? void 0 : opts.logger) !== null && _a !== void 0 ? _a : (0, logger_1.makeLogger)();
141
+ async function setFigmaBaseline(configuration, figmaBaselines, logger, opts) {
142
+ logger = (0, logger_1.makeLogger)({ logger, format: { label: 'figma' } });
130
143
  const mode = (0, utils_1.resolveFigmaMode)(opts === null || opts === void 0 ? void 0 : opts.mode);
131
144
  if (mode === 'disabled') {
132
145
  logger.log('setFigmaBaseline: disabled (APPLITOOLS_FIGMA_MODE=disabled) — skipping.');
@@ -145,9 +158,9 @@ async function setFigmaBaseline(configuration, figmaBaselines, opts) {
145
158
  nodesCacheTtlSeconds: opts === null || opts === void 0 ? void 0 : opts.nodesCacheTtlSeconds,
146
159
  maxRateLimitWaitSeconds: opts === null || opts === void 0 ? void 0 : opts.maxRateLimitWaitSeconds,
147
160
  skipApi: opts === null || opts === void 0 ? void 0 : opts.skipApi,
148
- proxy: opts === null || opts === void 0 ? void 0 : opts.proxy,
149
- req: opts === null || opts === void 0 ? void 0 : opts.req,
150
- logger,
161
+ // The instance enforces the wait budget and figma-rest names it in the error, so both are given
162
+ // the same value — unset, they fall back to the one DEFAULT_MAX_RATE_LIMIT_WAIT_SECONDS.
163
+ req: (0, client_1.makeFigmaReq)({ proxy: opts === null || opts === void 0 ? void 0 : opts.proxy, logger, maxRateLimitWaitSeconds: opts === null || opts === void 0 ? void 0 : opts.maxRateLimitWaitSeconds }),
151
164
  };
152
165
  const deps = {
153
166
  fetchFigmaImages: urlList => (0, client_1.fetchFigmaImages)(urlList, getToken(), restOpts),
@@ -159,10 +172,11 @@ async function setFigmaBaseline(configuration, figmaBaselines, opts) {
159
172
  const figmaUrls = (0, utils_1.resolveFigmaUrls)(figmaBaselines, configuration.testName);
160
173
  if (figmaUrls === null)
161
174
  return;
162
- const updated = await setFigmaBaselineFlow(figmaUrls, configuration, deps, mode, {
175
+ warnExperimental(logger);
176
+ const updated = await setFigmaBaselineFlow(figmaUrls, configuration, deps, logger, mode, {
163
177
  maxViewportHeight: opts === null || opts === void 0 ? void 0 : opts.maxViewportHeight,
164
178
  minViewportSize: opts === null || opts === void 0 ? void 0 : opts.minViewportSize,
165
- }, logger);
179
+ });
166
180
  Object.assign(configuration, updated);
167
181
  }
168
182
  exports.setFigmaBaseline = setFigmaBaseline;
@@ -45,6 +45,7 @@ async function applyFigmaBaselines(settings, logger, resolve = figma_1.setFigmaB
45
45
  delete settings.figmaOptions;
46
46
  return;
47
47
  }
48
+ logger = logger.extend({ label: 'figma', tags: [`figma-${utils.general.shortid()}`] });
48
49
  try {
49
50
  // A shared open config applies to every test, so an unmapped test must not fail — only skip.
50
51
  if (typeof figmaBaselines !== 'string' && figmaBaselines[settings.testName] === undefined) {
@@ -63,8 +64,7 @@ async function applyFigmaBaselines(settings, logger, resolve = figma_1.setFigmaB
63
64
  const accessToken = (_e = figmaOptions.accessToken) !== null && _e !== void 0 ? _e : process.env.FIGMA_ACCESS_TOKEN;
64
65
  if (accessToken)
65
66
  logger.mask(accessToken);
66
- await resolve(settings, figmaBaselines, {
67
- logger,
67
+ await resolve(settings, figmaBaselines, logger, {
68
68
  skipApi,
69
69
  mode: (0, utils_1.resolveFigmaMode)((_f = figmaOptions.mode) !== null && _f !== void 0 ? _f : utils.general.getEnvValue('FIGMA_MODE')),
70
70
  accessToken,
package/dist/lang.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.applitoolsLibConflictingCapabilitiesWarning = exports.requestedEnvironmentOutOfDriverBoundsWarning = exports.breakpointOutOfDriverBoundsWarning = exports.requestedEnvironmentSmallerThenBreakpointWarning = exports.outdatedSdkVersionWarning = exports.edgeUfgEnvironmentDeprecatedWarning = exports.matchLevelContentDeprecatedWarning = void 0;
3
+ exports.applitoolsLibConflictingCapabilitiesWarning = exports.requestedEnvironmentOutOfDriverBoundsWarning = exports.breakpointOutOfDriverBoundsWarning = exports.requestedEnvironmentSmallerThenBreakpointWarning = exports.outdatedSdkVersionWarning = exports.figmaExperimentalWarning = exports.edgeUfgEnvironmentDeprecatedWarning = exports.matchLevelContentDeprecatedWarning = void 0;
4
4
  exports.matchLevelContentDeprecatedWarning = `The "Content" match level value has been deprecated, use "IgnoreColors" instead.`;
5
5
  exports.edgeUfgEnvironmentDeprecatedWarning = `The 'edge' option that is being used in your browsers' configuration will soon be deprecated. Please change it to either 'edgelegacy' for the legacy version or to 'edgechromium' for the new Chromium-based version. Please note, when using the built-in BrowserType enum, then the values are BrowserType.EDGE_LEGACY and BrowserType.EDGE_CHROMIUM, respectively.`;
6
+ exports.figmaExperimentalWarning = `Figma support is experimental. It is not officially supported and may change or be removed in a future release.`;
6
7
  const outdatedSdkVersionWarning = (options) => {
7
8
  return `You are using "${options.currentVersion}" and there is a newer version "${options.latestVersion}" of the "${options.name}" SDK.`;
8
9
  };
package/dist/open-eyes.js CHANGED
@@ -184,8 +184,8 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
184
184
  var _a, _b;
185
185
  return ({
186
186
  getTypedEyes,
187
- check: (0, check_1.makeCheck)({ type, eyes, target: driver, spec, logger }),
188
- checkAndClose: (0, check_and_close_1.makeCheckAndClose)({ type, eyes, target: driver, spec, logger }),
187
+ check: (0, check_1.makeCheck)({ type, eyes, target: driver, environments: settings.environments, spec, logger }),
188
+ checkAndClose: (0, check_and_close_1.makeCheckAndClose)({ type, eyes, target: driver, environments: settings.environments, spec, logger }),
189
189
  close: (0, close_1.makeClose)({ eyes, environments: (_a = config === null || config === void 0 ? void 0 : config.check) === null || _a === void 0 ? void 0 : _a.environments, logger }),
190
190
  abort: (0, abort_1.makeAbort)({ eyes, environments: (_b = config === null || config === void 0 ? void 0 : config.check) === null || _b === void 0 ? void 0 : _b.environments, logger }),
191
191
  getResults: (0, get_eyes_results_1.makeGetEyesResults)({ eyes, logger }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applitools/core",
3
- "version": "4.68.0",
3
+ "version": "4.69.0",
4
4
  "homepage": "https://applitools.com",
5
5
  "bugs": {
6
6
  "url": "https://github.com/applitools/eyes.sdk.javascript1/issues"
@@ -61,20 +61,20 @@
61
61
  "setup:standalone": "sh -c 'yarn chromedriver --port=4444 --verbose &'"
62
62
  },
63
63
  "dependencies": {
64
- "@applitools/core-base": "1.38.0",
65
- "@applitools/dom-capture": "11.8.4",
66
- "@applitools/dom-snapshot": "4.17.8",
67
- "@applitools/driver": "1.26.7",
68
- "@applitools/ec-client": "1.12.41",
69
- "@applitools/image": "1.3.0",
70
- "@applitools/logger": "2.3.0",
71
- "@applitools/nml-client": "1.11.39",
72
- "@applitools/req": "1.11.2",
73
- "@applitools/screenshoter": "3.12.27",
74
- "@applitools/snippets": "2.9.5",
75
- "@applitools/socket": "1.3.15",
76
- "@applitools/ufg-client": "1.23.0",
77
- "@applitools/utils": "1.15.1",
64
+ "@applitools/core-base": "1.38.1",
65
+ "@applitools/dom-capture": "11.8.5",
66
+ "@applitools/dom-snapshot": "4.18.0",
67
+ "@applitools/driver": "1.26.9",
68
+ "@applitools/ec-client": "1.12.43",
69
+ "@applitools/image": "1.3.1",
70
+ "@applitools/logger": "2.3.1",
71
+ "@applitools/nml-client": "1.12.0",
72
+ "@applitools/req": "1.11.3",
73
+ "@applitools/screenshoter": "3.12.29",
74
+ "@applitools/snippets": "2.9.7",
75
+ "@applitools/socket": "1.3.16",
76
+ "@applitools/ufg-client": "1.23.1",
77
+ "@applitools/utils": "1.15.2",
78
78
  "abort-controller": "3.0.0",
79
79
  "chalk": "4.1.2",
80
80
  "node-fetch": "2.6.7",
@@ -84,13 +84,13 @@
84
84
  },
85
85
  "devDependencies": {
86
86
  "@applitools/bongo": "^5.10.0",
87
- "@applitools/spec-driver-playwright": "^1.9.7",
88
- "@applitools/spec-driver-puppeteer": "^1.8.7",
89
- "@applitools/spec-driver-selenium": "^1.8.7",
90
- "@applitools/spec-driver-webdriver": "^1.6.7",
91
- "@applitools/test-server": "^1.4.6",
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",
91
+ "@applitools/test-server": "^1.4.7",
92
92
  "@applitools/test-utils": "^1.5.17",
93
- "@applitools/tunnel-client": "^1.12.2",
93
+ "@applitools/tunnel-client": "^1.12.3",
94
94
  "@swc-node/register": "^1.6.8",
95
95
  "@types/mocha": "^10.0.7",
96
96
  "@types/node": "^12.20.55",
@@ -1,14 +1,15 @@
1
- import type { Target, DriverTarget, Eyes, Config, CheckSettings, CloseSettings } from './types';
1
+ import type { Target, DriverTarget, Eyes, Config, CheckSettings, CloseSettings, Environment } from './types';
2
2
  import { type Logger } from '@applitools/logger';
3
3
  import { type SpecType, type SpecDriver } from '@applitools/driver';
4
4
  type Options<TSpec extends SpecType, TType extends 'classic' | 'ufg'> = {
5
5
  type?: TType;
6
6
  eyes: Eyes<TSpec, TType>;
7
7
  target?: DriverTarget<TSpec>;
8
+ environments?: Environment[];
8
9
  spec?: SpecDriver<TSpec>;
9
10
  logger: Logger;
10
11
  };
11
- export declare function makeCheckAndClose<TSpec extends SpecType, TDefaultType extends 'classic' | 'ufg'>({ type: defaultType, eyes, target: defaultTarget, spec, logger: mainLogger, }: Options<TSpec, TDefaultType>): <TType extends "classic" | "ufg" = TDefaultType>({ type, target, settings, config, logger, }?: {
12
+ export declare function makeCheckAndClose<TSpec extends SpecType, TDefaultType extends 'classic' | 'ufg'>({ type: defaultType, eyes, target: defaultTarget, environments: openEnvironments, spec, logger: mainLogger, }: Options<TSpec, TDefaultType>): <TType extends "classic" | "ufg" = TDefaultType>({ type, target, settings, config, logger, }?: {
12
13
  type?: TType | undefined;
13
14
  target?: Target<TSpec, TType> | undefined;
14
15
  settings?: (CheckSettings<TSpec, TDefaultType> & CloseSettings<TDefaultType> & CheckSettings<TSpec, TType> & CloseSettings<TType>) | undefined;
package/types/check.d.ts CHANGED
@@ -1,14 +1,15 @@
1
- import type { Target, DriverTarget, Eyes, Config, CheckSettings } from './types';
1
+ import type { Target, DriverTarget, Eyes, Config, CheckSettings, Environment } from './types';
2
2
  import { type Logger } from '@applitools/logger';
3
3
  import { type SpecType, type SpecDriver } from '@applitools/driver';
4
4
  type Options<TSpec extends SpecType, TType extends 'classic' | 'ufg'> = {
5
5
  type?: TType;
6
6
  eyes: Eyes<TSpec, TType>;
7
7
  target?: DriverTarget<TSpec>;
8
+ environments?: Environment[];
8
9
  spec?: SpecDriver<TSpec>;
9
10
  logger: Logger;
10
11
  };
11
- export declare function makeCheck<TSpec extends SpecType, TDefaultType extends 'classic' | 'ufg'>({ type: defaultType, eyes, target: defaultTarget, spec, logger: mainLogger, }: Options<TSpec, TDefaultType>): <TType extends "classic" | "ufg" = TDefaultType>({ type, target, settings, config, logger, }?: {
12
+ export declare function makeCheck<TSpec extends SpecType, TDefaultType extends 'classic' | 'ufg'>({ type: defaultType, eyes, target: defaultTarget, environments: openEnvironments, spec, logger: mainLogger, }: Options<TSpec, TDefaultType>): <TType extends "classic" | "ufg" = TDefaultType>({ type, target, settings, config, logger, }?: {
12
13
  type?: TType | undefined;
13
14
  target?: Target<TSpec, TType> | undefined;
14
15
  settings?: CheckSettings<TSpec, TType> | undefined;
@@ -1,9 +1,35 @@
1
- import { type Req, type Proxy } from '@applitools/req';
1
+ import { type Req, type Proxy, type Headers } from '@applitools/req';
2
2
  import { type Logger } from '@applitools/logger';
3
3
  export declare const FIGMA_API = "https://api.figma.com/v1";
4
+ export declare const DEFAULT_MAX_RATE_LIMIT_WAIT_SECONDS = 60;
5
+ /** Undefined when Figma sent no `Retry-After`, or one this doesn't parse (e.g. an HTTP-date). */
6
+ export declare function parseRetryAfterSeconds(headers: Headers): number | undefined;
7
+ /**
8
+ * Retrying a 429 can end three ways, and they have different remedies — the wait budget is the binding
9
+ * constraint in only two of them. Whoever observes the ending names it; nothing infers it after the fact.
10
+ */
11
+ export type FigmaRateLimitEnding =
12
+ /** Figma asked for longer than the budget allows, so no retry was attempted. */
13
+ {
14
+ reason: 'retry-after-over-budget';
15
+ retryAfterSeconds: number;
16
+ }
17
+ /** Retried up to the attempt limit and Figma was still throttling. */
18
+ | {
19
+ reason: 'attempts-exhausted';
20
+ }
21
+ /** The waits themselves consumed the budget, so `req` abandoned the retry loop. */
22
+ | {
23
+ reason: 'wait-budget-exhausted';
24
+ };
25
+ /**
26
+ * The single wording for "Figma throttled us and we stopped trying", shared by the log line here and by
27
+ * the errors `figma-rest` throws — a user who sees only one of the two still gets the whole cause.
28
+ */
29
+ export declare function figmaRateLimitMessage(ending: FigmaRateLimitEnding, maxRateLimitWaitSeconds: number): string;
4
30
  export type FigmaReqOptions = {
5
31
  proxy?: Proxy;
6
32
  logger?: Logger;
7
33
  maxRateLimitWaitSeconds?: number;
8
34
  };
9
- export declare function makeFigmaReq({ proxy, logger, maxRateLimitWaitSeconds }?: FigmaReqOptions): Req;
35
+ export declare function makeFigmaReq({ proxy, logger, maxRateLimitWaitSeconds, }?: FigmaReqOptions): Req;
@@ -1,16 +1,13 @@
1
- import { type Logger } from '@applitools/logger';
2
- import type { Proxy, Req } from '@applitools/req';
1
+ import { type Req } from '@applitools/req';
3
2
  import type { FigmaImageResult, FigmaNodeMetadata } from '../types';
4
3
  export type FigmaRestOptions = {
4
+ req: Req;
5
5
  tempPath?: string;
6
6
  nodesCacheTtlSeconds?: number;
7
+ /** Must be the budget `req` was built with: `req` enforces the wait, this names it in the error. */
7
8
  maxRateLimitWaitSeconds?: number;
8
9
  /** Serve from the on-disk caches only; throw on any miss. */
9
10
  skipApi?: boolean;
10
- proxy?: Proxy;
11
- /** Defaults to `makeFigmaReq(…)`; tests inject a stub. */
12
- req?: Req;
13
- logger?: Logger;
14
11
  };
15
12
  /**
16
13
  * Cache names are hashed because Figma node ids are not filename-safe — an instance node id looks like
@@ -20,5 +17,5 @@ export type FigmaRestOptions = {
20
17
  */
21
18
  export declare function figmaNodesCacheFileName(fileId: string, uniqueNodeIds: string[]): string;
22
19
  export declare function figmaImageCacheFileName(fileId: string, nodeId: string, lastModifiedUnix: number): string;
23
- export declare function fetchFigmaMetadata(urls: string[], accessToken: string, opts?: FigmaRestOptions): Promise<FigmaNodeMetadata[]>;
24
- export declare function fetchFigmaImages(urls: string[], accessToken: string, opts?: FigmaRestOptions): Promise<FigmaImageResult[]>;
20
+ export declare function fetchFigmaMetadata(urls: string[], accessToken: string, opts: FigmaRestOptions): Promise<FigmaNodeMetadata[]>;
21
+ export declare function fetchFigmaImages(urls: string[], accessToken: string, opts: FigmaRestOptions): Promise<FigmaImageResult[]>;
@@ -1,10 +1,10 @@
1
1
  import { type Logger } from '@applitools/logger';
2
- import type { Proxy, Req } from '@applitools/req';
2
+ import type { Proxy } from '@applitools/req';
3
3
  import type { OpenSettings } from '../automation/types';
4
4
  import { type FigmaMode } from './utils';
5
5
  import type { FigmaViewportOptions, FigmaBaselineDeps } from './types';
6
- export declare function setFigmaBaselineFlow(figmaUrls: string[], originalConfig: Partial<OpenSettings>, deps: FigmaBaselineDeps, mode?: Exclude<FigmaMode, 'disabled'>, viewportOpts?: FigmaViewportOptions, logger?: Logger): Promise<Partial<OpenSettings>>;
7
- export declare function setFigmaBaseline(configuration: Partial<OpenSettings>, figmaBaselines: string | Record<string, string | string[]>, opts?: {
6
+ export declare function setFigmaBaselineFlow(figmaUrls: string[], originalConfig: Partial<OpenSettings>, deps: FigmaBaselineDeps, logger: Logger, mode?: Exclude<FigmaMode, 'disabled'>, viewportOpts?: FigmaViewportOptions): Promise<Partial<OpenSettings>>;
7
+ export declare function setFigmaBaseline(configuration: Partial<OpenSettings>, figmaBaselines: string | Record<string, string | string[]>, logger: Logger, opts?: {
8
8
  accessToken?: string;
9
9
  maxRateLimitWaitSeconds?: number;
10
10
  nodesCacheTtlSeconds?: number;
@@ -15,7 +15,4 @@ export declare function setFigmaBaseline(configuration: Partial<OpenSettings>, f
15
15
  /** Serve from the disk caches only; throw on any miss. */
16
16
  skipApi?: boolean;
17
17
  proxy?: Proxy;
18
- /** Defaults to the client's own req; tests inject a stub. */
19
- req?: Req;
20
- logger?: Logger;
21
18
  }): Promise<void>;
package/types/lang.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export declare const matchLevelContentDeprecatedWarning = "The \"Content\" match level value has been deprecated, use \"IgnoreColors\" instead.";
2
2
  export declare const edgeUfgEnvironmentDeprecatedWarning = "The 'edge' option that is being used in your browsers' configuration will soon be deprecated. Please change it to either 'edgelegacy' for the legacy version or to 'edgechromium' for the new Chromium-based version. Please note, when using the built-in BrowserType enum, then the values are BrowserType.EDGE_LEGACY and BrowserType.EDGE_CHROMIUM, respectively.";
3
+ export declare const figmaExperimentalWarning = "Figma support is experimental. It is not officially supported and may change or be removed in a future release.";
3
4
  export declare const outdatedSdkVersionWarning: (options: {
4
5
  name: string;
5
6
  currentVersion: string;