@applitools/visual-grid-cli-utils 1.21.43 → 1.21.45
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/README.md +17 -0
- package/package.json +1 -1
- package/src/commons/build-safari-emulation-info.js +27 -0
- package/src/commons/fetch-raw-cdt-url.js +34 -0
- package/src/commons/parse-viewport.js +11 -0
- package/src/job-info.js +21 -5
- package/src/rerender.js +21 -5
- package/src/view-rendering.js +42 -1
- package/src/visual-grid-cli-utils.js +54 -1
package/README.md
CHANGED
|
@@ -13,3 +13,20 @@ npm install -g @applitools/visual-grid-cli-utils
|
|
|
13
13
|
## Using it
|
|
14
14
|
|
|
15
15
|
* To get all the options, do `visual-grid-cli-utils --help`
|
|
16
|
+
|
|
17
|
+
### `view-rendering <render-id> --raw`
|
|
18
|
+
|
|
19
|
+
* Instead of printing the view-rendering URL, `--raw` fetches a time-limited **signed URL to the
|
|
20
|
+
render's raw (unparsed) root-DOM CDT** and prints it together with its expiration.
|
|
21
|
+
* It calls the rendering-api-app `/render` endpoint with the `rawCdtOnly` option; the server
|
|
22
|
+
enforces ownership (you must own the render, or be an admin).
|
|
23
|
+
* Only renders created on or after **2026-06-26** are supported — the raw root-DOM CDT path
|
|
24
|
+
(`rdvsMetadata.rootDomRawResourcePath`) started being recorded with AD-14694. The feature is
|
|
25
|
+
not supported for renders created before then; the command prints a `404` hint explaining this.
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
vg-cli view-rendering <render-id> --raw
|
|
29
|
+
# Raw root-DOM CDT URL:
|
|
30
|
+
# https://storage.googleapis.com/rg-raw-.../<accountId>/<resourceId>?X-Goog-Signature=...
|
|
31
|
+
# Expires at: 2026-06-28T12:00:00.000Z (TTL 900s)
|
|
32
|
+
```
|
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function buildSafariEmulationInfo({
|
|
4
|
+
safariEmulationDeviceName,
|
|
5
|
+
safariEmulationOrientation,
|
|
6
|
+
safariEmulationViewport,
|
|
7
|
+
safariEmulationPixelRatio,
|
|
8
|
+
safariEmulationUserAgent,
|
|
9
|
+
}) {
|
|
10
|
+
const anyFlagSet = !!(
|
|
11
|
+
safariEmulationDeviceName ||
|
|
12
|
+
safariEmulationViewport ||
|
|
13
|
+
safariEmulationPixelRatio !== undefined ||
|
|
14
|
+
safariEmulationUserAgent
|
|
15
|
+
)
|
|
16
|
+
if (!anyFlagSet) return undefined
|
|
17
|
+
|
|
18
|
+
const info = {}
|
|
19
|
+
if (safariEmulationDeviceName) info.deviceName = safariEmulationDeviceName
|
|
20
|
+
if (safariEmulationViewport) info.viewport = safariEmulationViewport
|
|
21
|
+
if (safariEmulationPixelRatio !== undefined) info.pixelRatio = safariEmulationPixelRatio
|
|
22
|
+
if (safariEmulationUserAgent) info.userAgent = safariEmulationUserAgent
|
|
23
|
+
info.screenOrientation = safariEmulationOrientation || 'portrait'
|
|
24
|
+
return info
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = buildSafariEmulationInfo
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const {fetch: defaultFetch} = require('@applitools/http-commons')
|
|
3
|
+
|
|
4
|
+
// Fetch a time-limited signed URL for a render's raw (unparsed) root-DOM CDT by calling the
|
|
5
|
+
// rendering-api-app `/render` endpoint with the `rawCdtOnly` flag (AD-14682). The endpoint
|
|
6
|
+
// returns `{ url, expiresAt, ttlSeconds }` and enforces ownership server-side. `fetch` is
|
|
7
|
+
// injectable for testing; it defaults to the shared http-commons fetch.
|
|
8
|
+
async function fetchRawCdtUrl({vgUrl, renderId, authToken, agentId, fetch = defaultFetch}) {
|
|
9
|
+
const renderUrl = new URL('./render', vgUrl)
|
|
10
|
+
const response = await fetch(renderUrl, {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
headers: {
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
'X-Auth-Token': authToken,
|
|
15
|
+
},
|
|
16
|
+
body: JSON.stringify({
|
|
17
|
+
reRenderId: renderId,
|
|
18
|
+
rawCdtOnly: true,
|
|
19
|
+
agentId,
|
|
20
|
+
options: {
|
|
21
|
+
rawCdtOnly: true,
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const errorBody = await response.text()
|
|
28
|
+
throw new Error(`Failed to fetch raw CDT URL - HTTP ${response.status}: ${errorBody}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return response.json()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = fetchRawCdtUrl
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
function parseViewport(arg) {
|
|
4
|
+
const match = /^(\d+)x(\d+)$/i.exec(arg)
|
|
5
|
+
if (!match) {
|
|
6
|
+
throw new Error(`invalid viewport "${arg}". Expected format: WIDTHxHEIGHT (e.g., 393x695)`)
|
|
7
|
+
}
|
|
8
|
+
return {width: parseInt(match[1], 10), height: parseInt(match[2], 10)}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = parseViewport
|
package/src/job-info.js
CHANGED
|
@@ -3,6 +3,7 @@ const {fetchAsJsonWithJsonBody} = require('@applitools/http-commons')
|
|
|
3
3
|
const createSession = require('./commons/create-session')
|
|
4
4
|
const {convertBrowserNameToCanary} = require('./commons/parse-browser')
|
|
5
5
|
const parseJson = require('./commons/parse-json.js')
|
|
6
|
+
const buildSafariEmulationInfo = require('./commons/build-safari-emulation-info')
|
|
6
7
|
|
|
7
8
|
async function main({
|
|
8
9
|
apiKey,
|
|
@@ -13,6 +14,11 @@ async function main({
|
|
|
13
14
|
native,
|
|
14
15
|
deviceEmulation,
|
|
15
16
|
deviceOrientation,
|
|
17
|
+
safariEmulationDeviceName,
|
|
18
|
+
safariEmulationOrientation,
|
|
19
|
+
safariEmulationViewport,
|
|
20
|
+
safariEmulationPixelRatio,
|
|
21
|
+
safariEmulationUserAgent,
|
|
16
22
|
iosDeviceName,
|
|
17
23
|
iosDeviceOrientation,
|
|
18
24
|
androidDeviceName,
|
|
@@ -29,6 +35,14 @@ async function main({
|
|
|
29
35
|
options.chromeHeadless = headless
|
|
30
36
|
const vgUrl = process.env.VG_URL || 'https://render-wus.applitools.com'
|
|
31
37
|
|
|
38
|
+
const safariEmulationInfo = buildSafariEmulationInfo({
|
|
39
|
+
safariEmulationDeviceName,
|
|
40
|
+
safariEmulationOrientation,
|
|
41
|
+
safariEmulationViewport,
|
|
42
|
+
safariEmulationPixelRatio,
|
|
43
|
+
safariEmulationUserAgent,
|
|
44
|
+
})
|
|
45
|
+
|
|
32
46
|
const {webhook, authToken} = await createSession(apiKey, serverUrl)
|
|
33
47
|
|
|
34
48
|
const renderInfoUrl = new URL('./job-info', vgUrl)
|
|
@@ -38,9 +52,11 @@ async function main({
|
|
|
38
52
|
accountOverride,
|
|
39
53
|
webhook,
|
|
40
54
|
renderInfo: {
|
|
41
|
-
emulationInfo:
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
emulationInfo:
|
|
56
|
+
safariEmulationInfo ||
|
|
57
|
+
(deviceEmulation
|
|
58
|
+
? {deviceName: deviceEmulation, screenOrientation: deviceOrientation}
|
|
59
|
+
: undefined),
|
|
44
60
|
iosDeviceInfo: iosDeviceName
|
|
45
61
|
? {
|
|
46
62
|
name: iosDeviceName,
|
|
@@ -56,8 +72,8 @@ async function main({
|
|
|
56
72
|
}
|
|
57
73
|
: undefined,
|
|
58
74
|
vhsType: androidDeviceName ? androidVhsType : undefined,
|
|
59
|
-
width: deviceEmulation || iosDeviceName ? undefined : width,
|
|
60
|
-
height: deviceEmulation || iosDeviceName ? undefined : height,
|
|
75
|
+
width: deviceEmulation || iosDeviceName || safariEmulationInfo ? undefined : width,
|
|
76
|
+
height: deviceEmulation || iosDeviceName || safariEmulationInfo ? undefined : height,
|
|
61
77
|
target: 'full-page',
|
|
62
78
|
},
|
|
63
79
|
agentId: `visual-grid-cli-utils/${require('../package.json').version}`,
|
package/src/rerender.js
CHANGED
|
@@ -12,6 +12,7 @@ const {convertBrowserNameToCanary} = require('./commons/parse-browser')
|
|
|
12
12
|
const parseJson = require('./commons/parse-json.js')
|
|
13
13
|
const normalizeChainedSelectorsArray = require('./commons/normalizeChainedSelectorsArray')
|
|
14
14
|
const normalizeChainedSelectorsToFindRegionsForArray = require('./commons/normalizeChainedSelectorsToFindRegionsForArray')
|
|
15
|
+
const buildSafariEmulationInfo = require('./commons/build-safari-emulation-info')
|
|
15
16
|
|
|
16
17
|
async function main({
|
|
17
18
|
apiKey,
|
|
@@ -26,6 +27,11 @@ async function main({
|
|
|
26
27
|
headless,
|
|
27
28
|
deviceEmulation,
|
|
28
29
|
deviceOrientation,
|
|
30
|
+
safariEmulationDeviceName,
|
|
31
|
+
safariEmulationOrientation,
|
|
32
|
+
safariEmulationViewport,
|
|
33
|
+
safariEmulationPixelRatio,
|
|
34
|
+
safariEmulationUserAgent,
|
|
29
35
|
androidDeviceName,
|
|
30
36
|
androidVersion,
|
|
31
37
|
androidDeviceOrientation,
|
|
@@ -64,6 +70,14 @@ async function main({
|
|
|
64
70
|
options.chromeHeadless = headless
|
|
65
71
|
const vgUrl = process.env.VG_URL || 'https://render-wus.applitools.com'
|
|
66
72
|
|
|
73
|
+
const safariEmulationInfo = buildSafariEmulationInfo({
|
|
74
|
+
safariEmulationDeviceName,
|
|
75
|
+
safariEmulationOrientation,
|
|
76
|
+
safariEmulationViewport,
|
|
77
|
+
safariEmulationPixelRatio,
|
|
78
|
+
safariEmulationUserAgent,
|
|
79
|
+
})
|
|
80
|
+
|
|
67
81
|
const finalOutputFile = outputFile || path.join(fs.mkdtempSync(os.tmpdir() + '/'), 'rerender.png')
|
|
68
82
|
const domSnapshotOutputFile = finalOutputFile.replace(/\.[a-zA-Z0-0]+$/, '.json')
|
|
69
83
|
const domSnapshotHtml = finalOutputFile.replace(/\.[a-zA-Z0-0]+$/, '.html')
|
|
@@ -75,9 +89,11 @@ async function main({
|
|
|
75
89
|
accountOverride,
|
|
76
90
|
webhook,
|
|
77
91
|
renderInfo: {
|
|
78
|
-
emulationInfo:
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
emulationInfo:
|
|
93
|
+
safariEmulationInfo ||
|
|
94
|
+
(deviceEmulation
|
|
95
|
+
? {deviceName: deviceEmulation, screenOrientation: deviceOrientation}
|
|
96
|
+
: undefined),
|
|
81
97
|
iosDeviceInfo: iosDeviceName
|
|
82
98
|
? {
|
|
83
99
|
name: iosDeviceName,
|
|
@@ -99,8 +115,8 @@ async function main({
|
|
|
99
115
|
}
|
|
100
116
|
: undefined,
|
|
101
117
|
vhsType: androidDeviceName ? androidVhsType : undefined,
|
|
102
|
-
width: deviceEmulation || iosDeviceName ? undefined : width,
|
|
103
|
-
height: deviceEmulation || iosDeviceName ? undefined : height,
|
|
118
|
+
width: deviceEmulation || iosDeviceName || safariEmulationInfo ? undefined : width,
|
|
119
|
+
height: deviceEmulation || iosDeviceName || safariEmulationInfo ? undefined : height,
|
|
104
120
|
target,
|
|
105
121
|
selector: selector
|
|
106
122
|
? {selector, type: selector.startsWith('//') ? 'xpath' : 'css'}
|
package/src/view-rendering.js
CHANGED
|
@@ -3,6 +3,7 @@ const {exec} = require('child_process')
|
|
|
3
3
|
const retry = require('p-retry')
|
|
4
4
|
const createSession = require('./commons/create-session')
|
|
5
5
|
const calculateViewRenderingUrl = require('./commons/view-rendering-url')
|
|
6
|
+
const fetchRawCdtUrl = require('./commons/fetch-raw-cdt-url')
|
|
6
7
|
const {fetch, throwErrorFromBadStatus} = require('@applitools/http-commons')
|
|
7
8
|
const fs = require('fs')
|
|
8
9
|
|
|
@@ -52,7 +53,9 @@ async function waitForParsedStatus(vgUrl, renderId, authToken, waitTimeout = 5 *
|
|
|
52
53
|
)
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
+
// `_authToken` is unused — this path mints its own session below; the `_` prefix satisfies the
|
|
57
|
+
// no-unused-vars rule (was a pre-existing lint error on this line before the --raw change).
|
|
58
|
+
async function tryNewViewRenderingEndpoint(vgUrl, renderId, _authToken, apiKey, serverUrl) {
|
|
56
59
|
try {
|
|
57
60
|
console.log(`[1/3] Starting resource parsing for render: ${renderId}`)
|
|
58
61
|
// Step 1: Call rerender API with parseResourcesOnly flag
|
|
@@ -121,6 +124,7 @@ async function main({
|
|
|
121
124
|
serverUrl,
|
|
122
125
|
accountOverride,
|
|
123
126
|
downloadVhsFolder,
|
|
127
|
+
raw,
|
|
124
128
|
}) {
|
|
125
129
|
if (!apiKey) {
|
|
126
130
|
throw new Error(
|
|
@@ -134,6 +138,43 @@ async function main({
|
|
|
134
138
|
|
|
135
139
|
const vgUrl = process.env.VG_URL || 'https://render-wus.applitools.com'
|
|
136
140
|
|
|
141
|
+
// --raw: fetch a signed URL to the render's raw root-DOM CDT (via the rawCdtOnly /render
|
|
142
|
+
// option, AD-14682) and print it + its expiration, instead of the view-rendering URL.
|
|
143
|
+
if (raw) {
|
|
144
|
+
const agentId = `visual-grid-cli-utils/${require('../package.json').version}`
|
|
145
|
+
let rawCdt
|
|
146
|
+
try {
|
|
147
|
+
rawCdt = await fetchRawCdtUrl({
|
|
148
|
+
vgUrl,
|
|
149
|
+
renderId,
|
|
150
|
+
authToken: finalAuthToken,
|
|
151
|
+
agentId,
|
|
152
|
+
})
|
|
153
|
+
} catch (err) {
|
|
154
|
+
// A 404 most commonly means the render has no stored raw root-DOM CDT path. That path
|
|
155
|
+
// (rdvsMetadata.rootDomRawResourcePath) only started being recorded with AD-14694, so
|
|
156
|
+
// the feature isn't supported for renders created before then — surface a gentle hint.
|
|
157
|
+
if (String(err.message).includes('HTTP 404')) {
|
|
158
|
+
console.log('\n⚠ No raw root-DOM CDT URL is available for this render (404).')
|
|
159
|
+
console.log(
|
|
160
|
+
' This feature supports renders created on or after 2026-06-26; it is not supported',
|
|
161
|
+
)
|
|
162
|
+
console.log(' for renders created before then.')
|
|
163
|
+
console.log(`\n Server response: ${err.message}`)
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
throw err
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const {url, expiresAt, ttlSeconds} = rawCdt
|
|
170
|
+
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
171
|
+
console.log('Raw root-DOM CDT URL:')
|
|
172
|
+
console.log(url)
|
|
173
|
+
console.log(`Expires at: ${expiresAt} (TTL ${ttlSeconds}s)`)
|
|
174
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
137
178
|
// Try new view-rendering endpoint with rerender API
|
|
138
179
|
const result = await tryNewViewRenderingEndpoint(
|
|
139
180
|
vgUrl,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const yargs = require('yargs')
|
|
3
3
|
const {parseBrowser} = require('./commons/parse-browser')
|
|
4
4
|
const parseRegion = require('./commons/parse-region')
|
|
5
|
+
const parseViewport = require('./commons/parse-viewport')
|
|
5
6
|
const splitApiKey = require('./commons/split-api-key')
|
|
6
7
|
|
|
7
8
|
async function main(argv, {shouldExitOnError = false} = {}) {
|
|
@@ -39,6 +40,12 @@ async function main(argv, {shouldExitOnError = false} = {}) {
|
|
|
39
40
|
.option('download-vhs-folder', {
|
|
40
41
|
describe: 'Specify a folder to which download the vhs file (in case of VHS snapshot)',
|
|
41
42
|
type: 'string',
|
|
43
|
+
})
|
|
44
|
+
.option('raw', {
|
|
45
|
+
describe:
|
|
46
|
+
"fetch a signed URL to the render's raw root-DOM CDT (and its expiration) instead of the view-rendering url",
|
|
47
|
+
default: false,
|
|
48
|
+
type: 'boolean',
|
|
42
49
|
}),
|
|
43
50
|
)
|
|
44
51
|
.command('view-resource <hash>', 'view resource url', (yargs) =>
|
|
@@ -128,6 +135,29 @@ async function main(argv, {shouldExitOnError = false} = {}) {
|
|
|
128
135
|
choices: ['portrait', 'landscape'],
|
|
129
136
|
default: 'portrait',
|
|
130
137
|
})
|
|
138
|
+
.option('safari-emulation-device-name', {
|
|
139
|
+
describe: 'the device name to use for Safari emulation (e.g., "iPhone 16")',
|
|
140
|
+
type: 'string',
|
|
141
|
+
})
|
|
142
|
+
.option('safari-emulation-orientation', {
|
|
143
|
+
describe: 'portrait or landscape for Safari emulation',
|
|
144
|
+
type: 'string',
|
|
145
|
+
choices: ['portrait', 'landscape'],
|
|
146
|
+
default: 'portrait',
|
|
147
|
+
})
|
|
148
|
+
.option('safari-emulation-viewport', {
|
|
149
|
+
describe: 'viewport for Safari emulation, in WIDTHxHEIGHT format (e.g., 393x695)',
|
|
150
|
+
type: 'string',
|
|
151
|
+
coerce: parseViewport,
|
|
152
|
+
})
|
|
153
|
+
.option('safari-emulation-pixel-ratio', {
|
|
154
|
+
describe: 'device pixel ratio for Safari emulation (e.g., 3)',
|
|
155
|
+
type: 'number',
|
|
156
|
+
})
|
|
157
|
+
.option('safari-emulation-user-agent', {
|
|
158
|
+
describe: 'user-agent string for Safari emulation',
|
|
159
|
+
type: 'string',
|
|
160
|
+
})
|
|
131
161
|
.option('android-device-name', {
|
|
132
162
|
describe: 'the name of the Android device to use',
|
|
133
163
|
type: 'string',
|
|
@@ -491,6 +521,29 @@ async function main(argv, {shouldExitOnError = false} = {}) {
|
|
|
491
521
|
choices: ['portrait', 'landscape'],
|
|
492
522
|
default: 'portrait',
|
|
493
523
|
})
|
|
524
|
+
.option('safari-emulation-device-name', {
|
|
525
|
+
describe: 'the device name to use for Safari emulation (e.g., "iPhone 16")',
|
|
526
|
+
type: 'string',
|
|
527
|
+
})
|
|
528
|
+
.option('safari-emulation-orientation', {
|
|
529
|
+
describe: 'portrait or landscape for Safari emulation',
|
|
530
|
+
type: 'string',
|
|
531
|
+
choices: ['portrait', 'landscape'],
|
|
532
|
+
default: 'portrait',
|
|
533
|
+
})
|
|
534
|
+
.option('safari-emulation-viewport', {
|
|
535
|
+
describe: 'viewport for Safari emulation, in WIDTHxHEIGHT format (e.g., 393x695)',
|
|
536
|
+
type: 'string',
|
|
537
|
+
coerce: parseViewport,
|
|
538
|
+
})
|
|
539
|
+
.option('safari-emulation-pixel-ratio', {
|
|
540
|
+
describe: 'device pixel ratio for Safari emulation (e.g., 3)',
|
|
541
|
+
type: 'number',
|
|
542
|
+
})
|
|
543
|
+
.option('safari-emulation-user-agent', {
|
|
544
|
+
describe: 'user-agent string for Safari emulation',
|
|
545
|
+
type: 'string',
|
|
546
|
+
})
|
|
494
547
|
.option('ios-device-name', {
|
|
495
548
|
describe: 'the name of the iOS device to use',
|
|
496
549
|
type: 'string',
|
|
@@ -810,7 +863,7 @@ async function main(argv, {shouldExitOnError = false} = {}) {
|
|
|
810
863
|
|
|
811
864
|
const args = /**@type {object}*/ (commandLineOptions.parse())
|
|
812
865
|
|
|
813
|
-
if (args._ && args._[0]) {
|
|
866
|
+
if (args._ && args._[0] && !args.help) {
|
|
814
867
|
try {
|
|
815
868
|
return await require(`./${args._[0]}`)(normalizeArgs(args))
|
|
816
869
|
} catch (err) {
|