@applitools/core 4.68.1 → 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 +54 -0
- package/dist/automation/utils/wait-for-lazy-load.js +18 -1
- package/dist/figma/client/figma-req.js +46 -9
- package/dist/figma/client/figma-rest.js +47 -18
- package/dist/figma/client/index.js +2 -2
- package/dist/figma/figma.js +23 -9
- package/dist/figma/seam.js +2 -2
- package/dist/lang.js +2 -1
- package/package.json +11 -11
- package/types/figma/client/figma-req.d.ts +28 -2
- package/types/figma/client/figma-rest.d.ts +5 -8
- package/types/figma/figma.d.ts +3 -6
- package/types/lang.d.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
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
|
+
|
|
3
57
|
## [4.68.1](https://github.com/Applitools-Dev/sdk/compare/js/core@4.68.0...js/core@4.68.1) (2026-08-10)
|
|
4
58
|
|
|
5
59
|
|
|
@@ -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;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
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 (
|
|
34
|
-
logger === null || logger === void 0 ? void 0 : logger.
|
|
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 (
|
|
39
|
-
logger === null || logger === void 0 ? void 0 : logger.log(`Figma rate limit — waiting ${
|
|
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
|
-
|
|
58
|
-
|
|
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
|
|
128
|
-
ttlSeconds: (_b = opts
|
|
129
|
-
skipApi: (_c = opts
|
|
130
|
-
|
|
131
|
-
|
|
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, {
|
|
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
|
-
|
|
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
|
|
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
|
|
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);
|
package/dist/figma/figma.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
|
@@ -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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
179
|
+
});
|
|
166
180
|
Object.assign(configuration, updated);
|
|
167
181
|
}
|
|
168
182
|
exports.setFigmaBaseline = setFigmaBaseline;
|
package/dist/figma/seam.js
CHANGED
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applitools/core",
|
|
3
|
-
"version": "4.
|
|
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"
|
|
@@ -63,15 +63,15 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@applitools/core-base": "1.38.1",
|
|
65
65
|
"@applitools/dom-capture": "11.8.5",
|
|
66
|
-
"@applitools/dom-snapshot": "4.
|
|
67
|
-
"@applitools/driver": "1.26.
|
|
68
|
-
"@applitools/ec-client": "1.12.
|
|
66
|
+
"@applitools/dom-snapshot": "4.18.0",
|
|
67
|
+
"@applitools/driver": "1.26.9",
|
|
68
|
+
"@applitools/ec-client": "1.12.43",
|
|
69
69
|
"@applitools/image": "1.3.1",
|
|
70
70
|
"@applitools/logger": "2.3.1",
|
|
71
|
-
"@applitools/nml-client": "1.
|
|
71
|
+
"@applitools/nml-client": "1.12.0",
|
|
72
72
|
"@applitools/req": "1.11.3",
|
|
73
|
-
"@applitools/screenshoter": "3.12.
|
|
74
|
-
"@applitools/snippets": "2.9.
|
|
73
|
+
"@applitools/screenshoter": "3.12.29",
|
|
74
|
+
"@applitools/snippets": "2.9.7",
|
|
75
75
|
"@applitools/socket": "1.3.16",
|
|
76
76
|
"@applitools/ufg-client": "1.23.1",
|
|
77
77
|
"@applitools/utils": "1.15.2",
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@applitools/bongo": "^5.10.0",
|
|
87
|
-
"@applitools/spec-driver-playwright": "^1.9.
|
|
88
|
-
"@applitools/spec-driver-puppeteer": "^1.8.
|
|
89
|
-
"@applitools/spec-driver-selenium": "^1.8.
|
|
90
|
-
"@applitools/spec-driver-webdriver": "^1.6.
|
|
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
91
|
"@applitools/test-server": "^1.4.7",
|
|
92
92
|
"@applitools/test-utils": "^1.5.17",
|
|
93
93
|
"@applitools/tunnel-client": "^1.12.3",
|
|
@@ -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
|
|
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
|
|
24
|
-
export declare function fetchFigmaImages(urls: string[], accessToken: string, opts
|
|
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[]>;
|
package/types/figma/figma.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type Logger } from '@applitools/logger';
|
|
2
|
-
import type { Proxy
|
|
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
|
|
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;
|