screenci 0.0.72 → 0.0.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +129 -74
  5. package/dist/cli.js.map +1 -1
  6. package/dist/src/asset.d.ts +74 -28
  7. package/dist/src/asset.d.ts.map +1 -1
  8. package/dist/src/asset.js +83 -14
  9. package/dist/src/asset.js.map +1 -1
  10. package/dist/src/config.d.ts +1 -1
  11. package/dist/src/config.d.ts.map +1 -1
  12. package/dist/src/config.js +13 -7
  13. package/dist/src/config.js.map +1 -1
  14. package/dist/src/defaults.d.ts +2 -2
  15. package/dist/src/defaults.d.ts.map +1 -1
  16. package/dist/src/defaults.js +2 -2
  17. package/dist/src/defaults.js.map +1 -1
  18. package/dist/src/init.d.ts.map +1 -1
  19. package/dist/src/init.js +66 -26
  20. package/dist/src/init.js.map +1 -1
  21. package/dist/src/mouse.d.ts.map +1 -1
  22. package/dist/src/mouse.js +6 -0
  23. package/dist/src/mouse.js.map +1 -1
  24. package/dist/src/runtimeContext.d.ts +19 -0
  25. package/dist/src/runtimeContext.d.ts.map +1 -1
  26. package/dist/src/runtimeContext.js +11 -0
  27. package/dist/src/runtimeContext.js.map +1 -1
  28. package/dist/src/screenshot.d.ts.map +1 -1
  29. package/dist/src/screenshot.js +2 -0
  30. package/dist/src/screenshot.js.map +1 -1
  31. package/dist/src/stillCapture.d.ts +8 -1
  32. package/dist/src/stillCapture.d.ts.map +1 -1
  33. package/dist/src/stillCapture.js +11 -0
  34. package/dist/src/stillCapture.js.map +1 -1
  35. package/dist/src/types.d.ts +2 -2
  36. package/dist/src/types.d.ts.map +1 -1
  37. package/dist/src/types.js +1 -1
  38. package/dist/src/types.js.map +1 -1
  39. package/dist/src/video.d.ts.map +1 -1
  40. package/dist/src/video.js +7 -1
  41. package/dist/src/video.js.map +1 -1
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +1 -1
  44. package/skills/screenci/SKILL.md +12 -17
  45. package/skills/screenci/references/init.md +1 -1
  46. package/skills/screenci/references/record.md +6 -6
  47. package/dist/docs/video-sources/product-pitch.screenci.d.ts +0 -2
  48. package/dist/docs/video-sources/product-pitch.screenci.d.ts.map +0 -1
  49. package/dist/docs/video-sources/product-pitch.screenci.js +0 -201
  50. package/dist/docs/video-sources/product-pitch.screenci.js.map +0 -1
package/README.md CHANGED
@@ -41,7 +41,7 @@ already know Playwright locators, navigation, and waiting, you already know
41
41
  most of the automation layer.
42
42
 
43
43
  ```ts
44
- // videos/onboarding.screenci.ts
44
+ // recordings/onboarding.screenci.ts
45
45
  import { hide, speed, time, video } from 'screenci'
46
46
 
47
47
  video('Onboarding flow', async ({ page }) => {
package/dist/cli.d.ts CHANGED
@@ -45,6 +45,9 @@ export type StudioUploadNotice = {
45
45
  };
46
46
  export declare function formatStudioUrl(appUrl: string, projectId: string, videoId: string): string;
47
47
  type OrgPlan = 'free' | 'starter' | 'business';
48
+ export declare class SignInTimeoutError extends Error {
49
+ constructor(message: string);
50
+ }
48
51
  export declare function withUploadRetry<T>(fn: () => Promise<T>, signal: AbortSignal | undefined, maxAttempts?: number): Promise<T>;
49
52
  export declare function attachUploadAbortStdinListener(input: Pick<NodeJS.ReadStream, 'on' | 'off' | 'pause'>, onAbort: (signal: NodeJS.Signals) => void): () => void;
50
53
  export declare function collectUploadAssets(data: RecordingData, configDir: string): Promise<PreparedUploadAsset[]>;
@@ -53,6 +56,7 @@ export declare function annotateRecordingDataWithAssetHashes(data: RecordingData
53
56
  export declare function formatUploadStartFailureMessage(videoName: string, status: number, responseText: string, secret: string): string;
54
57
  export declare function formatFailedVideoMessage(videoName: string, message: string): string;
55
58
  export declare function printUploadStartFailureMessage(videoName: string, status: number, responseText: string, secret: string): void;
59
+ export declare function displayAssetPath(assetPath: string): string;
56
60
  export declare function uploadRecordings(screenciDir: string, projectName: string, apiUrl: string, secret: string, specificEntry?: string, verbose?: boolean): Promise<{
57
61
  projectId: string | null;
58
62
  recordId: string | null;
@@ -73,12 +77,14 @@ export declare function extractGrep(args: string[]): string | undefined;
73
77
  export declare function ensureScreenciSecret(resolvedConfigPath?: string, opts?: {
74
78
  interactive?: boolean;
75
79
  pollAuth?: boolean;
80
+ noPollAuth?: boolean;
76
81
  }): Promise<string | undefined>;
77
82
  export declare function main(): Promise<void>;
78
83
  export declare function parseRecordCliArgs(args: string[]): {
79
84
  configPath: string | undefined;
80
85
  verbose: boolean;
81
86
  pollAuth: boolean;
87
+ noPollAuth: boolean;
82
88
  remote: boolean;
83
89
  languages: string | undefined;
84
90
  otherArgs: string[];
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../cli.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync,
4
4
  import { createHash, randomUUID } from 'crypto';
5
5
  import { createRequire } from 'module';
6
6
  import { appendFile, readdir, readFile, stat, writeFile } from 'fs/promises';
7
- import { delimiter, dirname, relative as pathRelative, resolve } from 'path';
7
+ import { delimiter, dirname, isAbsolute, relative as pathRelative, resolve, } from 'path';
8
8
  import { fileURLToPath, pathToFileURL } from 'url';
9
9
  import { Command, CommanderError } from 'commander';
10
10
  import pc from 'picocolors';
@@ -27,11 +27,15 @@ const SCREENCI_LINK_SESSION_POLL_INTERVAL_MS = 2_000;
27
27
  // sign-in. We poll on a slower cadence than the interactive loop so a long wait
28
28
  // for a human to click the link does not hammer the backend.
29
29
  const SCREENCI_LINK_SESSION_POLL_FLAG_INTERVAL_MS = 5_000;
30
- // `record --poll-auth` does not wait forever: after this long without a
31
- // completed sign-in we stop polling and exit cleanly so an agent or CI step does
32
- // not hang indefinitely. The link stays valid, so the command can be rerun. The
33
- // default can be overridden with SCREENCI_POLL_AUTH_TIMEOUT_MS (milliseconds).
30
+ // A waiting `record` does not wait forever: after this long without a completed
31
+ // sign-in we stop polling so a non-interactive agent run does not hang. The link
32
+ // stays valid, so the command can be rerun. The default can be overridden with
33
+ // SCREENCI_POLL_AUTH_TIMEOUT_MS (milliseconds).
34
34
  const SCREENCI_LINK_SESSION_POLL_FLAG_TIMEOUT_MS = 5 * 60 * 1_000;
35
+ // An interactive `record` (a human at a terminal) waits longer before giving up,
36
+ // since a person may take a while to open the link and finish signing in. Also
37
+ // overridable with SCREENCI_POLL_AUTH_TIMEOUT_MS.
38
+ const SCREENCI_LINK_SESSION_INTERACTIVE_TIMEOUT_MS = 15 * 60 * 1_000;
35
39
  const require = createRequire(import.meta.url);
36
40
  /**
37
41
  * Reports whether the current session can complete an interactive browser
@@ -184,7 +188,7 @@ function resolveRecordingFileCandidates(filePath, configDir, sourceFilePath) {
184
188
  return [
185
189
  filePath,
186
190
  ...(sourceFileCandidate ? [sourceFileCandidate] : []),
187
- resolve(configDir, 'videos', filePath),
191
+ resolve(configDir, 'recordings', filePath),
188
192
  resolve(configDir, pathRelative('/app', filePath)),
189
193
  ];
190
194
  }
@@ -215,7 +219,9 @@ function contentTypeForPath(filePath) {
215
219
  return contentTypeMap[ext] ?? 'application/octet-stream';
216
220
  }
217
221
  export function formatStudioUrl(appUrl, projectId, videoId) {
218
- return `${appUrl}/project/${projectId}/video/${videoId}/studio`;
222
+ // The video hub resolves `?studio` to the right language page and scrolls it
223
+ // to Studio, so we never need to guess the language in the printed link.
224
+ return `${appUrl}/project/${projectId}/video/${videoId}?studio`;
219
225
  }
220
226
  class UploadAssetError extends Error {
221
227
  constructor(message) {
@@ -243,6 +249,17 @@ class RecordFailureHintError extends Error {
243
249
  this.cause = cause;
244
250
  }
245
251
  }
252
+ // Thrown when we wait for a browser sign-in and the deadline passes before it
253
+ // completes. The link stays valid, so the message tells the user to sign in and
254
+ // rerun. A timed-out sign-in means recording did not happen, so the caller maps
255
+ // this to a non-zero exit (it is a failure, not the clean print-and-exit handoff
256
+ // used by CI).
257
+ export class SignInTimeoutError extends Error {
258
+ constructor(message) {
259
+ super(message);
260
+ this.name = 'SignInTimeoutError';
261
+ }
262
+ }
246
263
  function isUploadCancelledError(err) {
247
264
  return (err instanceof UploadCancelledError ||
248
265
  (err instanceof Error &&
@@ -1017,6 +1034,13 @@ export function printUploadStartFailureMessage(videoName, status, responseText,
1017
1034
  }
1018
1035
  logger.warn(message);
1019
1036
  }
1037
+ export function displayAssetPath(assetPath) {
1038
+ if (!isAbsolute(assetPath)) {
1039
+ return assetPath;
1040
+ }
1041
+ const rel = pathRelative(process.cwd(), assetPath);
1042
+ return rel.length > 0 ? rel : assetPath;
1043
+ }
1020
1044
  async function uploadAssets(assets, apiUrl, secret, recordingId, signal, throwIfAborted, progressReporter) {
1021
1045
  const logInfo = (message) => {
1022
1046
  if (progressReporter) {
@@ -1050,7 +1074,7 @@ async function uploadAssets(assets, apiUrl, secret, recordingId, signal, throwIf
1050
1074
  }
1051
1075
  const checkBody = (await checkRes.json());
1052
1076
  if (checkBody.exists) {
1053
- logInfo(`${pc.green('✔')} Asset already exists: ${asset.path}`);
1077
+ logInfo(`${pc.green('✔')} Asset already exists: ${displayAssetPath(asset.path)}`);
1054
1078
  continue;
1055
1079
  }
1056
1080
  if (!asset.fileBuffer || !asset.contentType) {
@@ -1077,14 +1101,14 @@ async function uploadAssets(assets, apiUrl, secret, recordingId, signal, throwIf
1077
1101
  if (!res.ok) {
1078
1102
  const text = await res.text();
1079
1103
  if (res.status === 409 && text.includes('already exists')) {
1080
- logInfo(`${pc.green('✔')} Asset already exists: ${asset.path}`);
1104
+ logInfo(`${pc.green('✔')} Asset already exists: ${displayAssetPath(asset.path)}`);
1081
1105
  }
1082
1106
  else {
1083
1107
  throw new UploadAssetError(`Failed to upload asset ${asset.path}: ${res.status} ${text}${hint401(res.status, secret)}`);
1084
1108
  }
1085
1109
  }
1086
1110
  else {
1087
- logInfo(`Asset uploaded: ${asset.path}`);
1111
+ logInfo(`Asset uploaded: ${displayAssetPath(asset.path)}`);
1088
1112
  }
1089
1113
  }
1090
1114
  catch (err) {
@@ -1387,8 +1411,22 @@ async function loadRecordConfigWithoutPlaywrightCollision(resolvedConfigPath) {
1387
1411
  }
1388
1412
  async function requireScreenCISecret(configPath, opts = {}) {
1389
1413
  const { resolvedConfigPath, screenciConfig } = await loadScreenCIConfigAndEnv(configPath);
1390
- const secret = process.env.SCREENCI_SECRET ??
1391
- (await ensureScreenciSecret(resolvedConfigPath, opts));
1414
+ let secret;
1415
+ try {
1416
+ secret =
1417
+ process.env.SCREENCI_SECRET ??
1418
+ (await ensureScreenciSecret(resolvedConfigPath, opts));
1419
+ }
1420
+ catch (err) {
1421
+ // A waited-out sign-in means recording did not happen: print the (already
1422
+ // formatted) message and exit non-zero so the failure is loud, instead of
1423
+ // looking like a successful run.
1424
+ if (err instanceof SignInTimeoutError) {
1425
+ logger.error(err.message);
1426
+ process.exit(1);
1427
+ }
1428
+ throw err;
1429
+ }
1392
1430
  if (!secret) {
1393
1431
  // In a non-interactive session ensureScreenciSecret already printed the
1394
1432
  // sign-in link and the next step, so we exit without repeating guidance.
@@ -1567,14 +1605,12 @@ async function pollLinkSessionOnce(spec) {
1567
1605
  }
1568
1606
  return { status };
1569
1607
  }
1570
- function getPollAuthTimeoutMs() {
1608
+ function getAuthPollTimeoutMs(defaultMs) {
1571
1609
  const raw = process.env.SCREENCI_POLL_AUTH_TIMEOUT_MS;
1572
1610
  if (raw === undefined)
1573
- return SCREENCI_LINK_SESSION_POLL_FLAG_TIMEOUT_MS;
1611
+ return defaultMs;
1574
1612
  const parsed = Number(raw);
1575
- return Number.isFinite(parsed) && parsed >= 0
1576
- ? parsed
1577
- : SCREENCI_LINK_SESSION_POLL_FLAG_TIMEOUT_MS;
1613
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : defaultMs;
1578
1614
  }
1579
1615
  async function pollLinkSession(spec, pollIntervalMs = SCREENCI_LINK_SESSION_POLL_INTERVAL_MS, deadlineEpochMs) {
1580
1616
  for (;;) {
@@ -1598,6 +1634,7 @@ async function pollLinkSession(spec, pollIntervalMs = SCREENCI_LINK_SESSION_POLL
1598
1634
  export async function ensureScreenciSecret(resolvedConfigPath, opts = {}) {
1599
1635
  const interactive = opts.interactive ?? true;
1600
1636
  const pollAuth = opts.pollAuth ?? false;
1637
+ const noPollAuth = opts.noPollAuth ?? false;
1601
1638
  const existingSecret = process.env.SCREENCI_SECRET;
1602
1639
  if (existingSecret)
1603
1640
  return existingSecret;
@@ -1639,69 +1676,80 @@ export async function ensureScreenciSecret(resolvedConfigPath, opts = {}) {
1639
1676
  logger.info(`Successfully saved SCREENCI_SECRET to ${envFilePath}`);
1640
1677
  return secret;
1641
1678
  };
1642
- if (!interactive) {
1643
- // Non-interactive sessions cannot complete a browser sign-in here, so by
1644
- // default we never block. Reuse or create the persisted session and check
1645
- // its status once; if a stored session is stale, recreate it and check
1646
- // once more. When the session is already completed (the sign-in happened
1647
- // between runs) we pick up the secret; otherwise we print the link and
1648
- // return so the caller can surface it and rerun later. The exception is
1649
- // `pollAuth` (the `--poll-auth` flag), which opts in to waiting: it keeps
1650
- // polling on a slow cadence until sign-in completes.
1651
- let spec = await ensureSpec();
1652
- let result = await pollLinkSessionOnce(spec);
1653
- if (result.status === 'expired' ||
1654
- result.status === 'consumed' ||
1655
- result.status === 'invalid') {
1656
- deletePersistedLinkSessionSpec(specPath);
1657
- spec = await ensureSpec();
1658
- result = await pollLinkSessionOnce(spec);
1659
- }
1660
- if (result.status === 'completed' && result.secret) {
1661
- return await saveCompletedSecret(result.secret);
1662
- }
1663
- if (pollAuth) {
1664
- // The caller asked us to wait for sign-in. Print the link, then poll on
1665
- // a slow cadence until the human finishes signing in, and continue
1666
- // recording automatically once the secret lands. We re-print the link
1667
- // on every (re)created session so the latest valid link is always
1668
- // visible, including after a stale session is recreated. We do not wait
1669
- // forever: after a default timeout we stop polling and exit cleanly so
1670
- // an agent or CI step does not hang. The link stays valid for a rerun.
1671
- const timeoutMs = getPollAuthTimeoutMs();
1672
- const timeoutMinutes = Math.round(timeoutMs / 60_000);
1673
- const deadlineEpochMs = Date.now() + timeoutMs;
1674
- for (;;) {
1675
- logger.info(`Sign-in required to record. Open this link to sign in:\n${pc.cyan(spec.appUrl)}\n` +
1676
- `Waiting for sign-in (checking every 5 seconds, up to ${timeoutMinutes} minutes). Recording continues automatically once you finish.`);
1677
- const polled = await pollLinkSession(spec, SCREENCI_LINK_SESSION_POLL_FLAG_INTERVAL_MS, deadlineEpochMs);
1678
- if (polled.status === 'completed' && polled.secret) {
1679
- return await saveCompletedSecret(polled.secret);
1680
- }
1681
- if (polled.status === 'timed-out' || Date.now() >= deadlineEpochMs) {
1682
- logger.info(`Timed out after ${timeoutMinutes} minutes waiting for sign-in. The link is still valid:\n${pc.cyan(spec.appUrl)}\n` +
1683
- `After signing in, rerun ${pc.cyan(getSuggestedScreenciCommand('record'))} to continue, or ${pc.cyan(getSuggestedScreenciCommand('record', '--poll-auth'))} to wait again.`);
1684
- return undefined;
1685
- }
1686
- deletePersistedLinkSessionSpec(specPath);
1687
- spec = await ensureSpec();
1688
- }
1689
- }
1679
+ // Decide whether this run waits for a browser sign-in or just prints the
1680
+ // link and hands off. A human at a terminal always waits. A plain
1681
+ // non-interactive run (a coding agent: no terminal, no CI) also waits by
1682
+ // default, so `npx screenci record` records without needing a flag. CI and
1683
+ // an explicit SCREENCI_NONINTERACTIVE never wait (CI must not hang and is
1684
+ // expected to provide SCREENCI_SECRET), and `--no-poll-auth` opts back into
1685
+ // that print-and-exit handoff. `--poll-auth` forces waiting and is kept for
1686
+ // backwards compatibility (it is the default for agents now).
1687
+ const isCI = process.env.CI === 'true';
1688
+ const nonInteractiveForced = process.env.SCREENCI_NONINTERACTIVE === '1';
1689
+ const waitForSignIn = noPollAuth
1690
+ ? false
1691
+ : interactive || pollAuth || (!isCI && !nonInteractiveForced);
1692
+ const pollIntervalMs = interactive
1693
+ ? SCREENCI_LINK_SESSION_POLL_INTERVAL_MS
1694
+ : SCREENCI_LINK_SESSION_POLL_FLAG_INTERVAL_MS;
1695
+ const timeoutMs = getAuthPollTimeoutMs(interactive
1696
+ ? SCREENCI_LINK_SESSION_INTERACTIVE_TIMEOUT_MS
1697
+ : SCREENCI_LINK_SESSION_POLL_FLAG_TIMEOUT_MS);
1698
+ // Check the current/stored session once. If sign-in already completed (for
1699
+ // example between runs) we pick up the secret immediately. Recreate a stale
1700
+ // session and check once more before deciding to wait or hand off.
1701
+ let spec = await ensureSpec();
1702
+ let result = await pollLinkSessionOnce(spec);
1703
+ if (result.status === 'expired' ||
1704
+ result.status === 'consumed' ||
1705
+ result.status === 'invalid') {
1706
+ deletePersistedLinkSessionSpec(specPath);
1707
+ spec = await ensureSpec();
1708
+ result = await pollLinkSessionOnce(spec);
1709
+ }
1710
+ if (result.status === 'completed' && result.secret) {
1711
+ return await saveCompletedSecret(result.secret);
1712
+ }
1713
+ if (!waitForSignIn) {
1714
+ // CI / SCREENCI_NONINTERACTIVE / --no-poll-auth: print the link and hand
1715
+ // off without waiting. The caller exits cleanly (0); a pending sign-in
1716
+ // here is an expected handoff, not a failure.
1690
1717
  logger.info(`Sign-in required to record. Open this link to sign in:\n${pc.cyan(spec.appUrl)}\n` +
1691
- `This session is non-interactive, so sign-in can't complete here. After signing in, rerun ${pc.cyan(getSuggestedScreenciCommand('record'))} to continue, or run ${pc.cyan(getSuggestedScreenciCommand('record', '--poll-auth'))} to print the link again and wait for sign-in, continuing automatically.`);
1718
+ `This session won't wait for sign-in. After signing in, rerun ${pc.cyan(getSuggestedScreenciCommand('record'))} to record. In CI, set SCREENCI_SECRET (from ${getScreenCISecretsUrl()}) to record without an interactive sign-in.`);
1692
1719
  return undefined;
1693
1720
  }
1721
+ // Wait for sign-in: print the link, then poll until the human finishes and
1722
+ // continue recording automatically once the secret lands. We re-print the
1723
+ // link on every (re)created session so the latest valid link is always
1724
+ // visible. We do not wait forever: after the timeout we throw so the caller
1725
+ // exits non-zero (a timed-out sign-in means nothing was recorded). The link
1726
+ // stays valid for a rerun.
1727
+ const timeoutMinutes = Math.max(1, Math.round(timeoutMs / 60_000));
1728
+ const intervalSeconds = Math.max(1, Math.round(pollIntervalMs / 1_000));
1729
+ const deadlineEpochMs = Date.now() + timeoutMs;
1694
1730
  for (;;) {
1695
- const spec = await ensureSpec();
1696
- logger.info(`Open this link to sign in and connect the CLI:\n${pc.cyan(spec.appUrl)}\n`);
1697
- const result = await pollLinkSession(spec);
1698
- if (result.status === 'completed' && result.secret) {
1699
- return await saveCompletedSecret(result.secret);
1731
+ logger.info(`Sign-in required to record. Open this link to sign in:\n${pc.cyan(spec.appUrl)}\n` +
1732
+ `Waiting for sign-in (checking every ${intervalSeconds} seconds, up to ${timeoutMinutes} minutes). Recording continues automatically once you finish.`);
1733
+ const polled = await pollLinkSession(spec, pollIntervalMs, deadlineEpochMs);
1734
+ if (polled.status === 'completed' && polled.secret) {
1735
+ return await saveCompletedSecret(polled.secret);
1700
1736
  }
1737
+ if (polled.status === 'timed-out' || Date.now() >= deadlineEpochMs) {
1738
+ throw new SignInTimeoutError(`Timed out after ${timeoutMinutes} minutes waiting for sign-in. The link is still valid:\n${pc.cyan(spec.appUrl)}\n` +
1739
+ `After signing in, rerun ${pc.cyan(getSuggestedScreenciCommand('record'))} to record.`);
1740
+ }
1741
+ // Session went stale (expired/consumed/invalid) before sign-in: recreate
1742
+ // and keep waiting until the deadline.
1701
1743
  deletePersistedLinkSessionSpec(specPath);
1744
+ spec = await ensureSpec();
1702
1745
  }
1703
1746
  }
1704
1747
  catch (err) {
1748
+ // A timed-out sign-in is a deliberate, already-formatted failure: let it
1749
+ // propagate so the caller can exit non-zero rather than swallowing it as a
1750
+ // generic auth error.
1751
+ if (err instanceof SignInTimeoutError)
1752
+ throw err;
1705
1753
  const msg = err instanceof Error ? err.message : String(err);
1706
1754
  logger.warn(`Authentication failed: ${msg}`);
1707
1755
  logger.info(`You can add SCREENCI_SECRET manually to ${resolvedConfigPath ? await resolveProjectEnvFilePath(resolvedConfigPath) : '.env'} later. Get it from ${getScreenCISecretsUrl()}.`);
@@ -1724,7 +1772,8 @@ export async function main() {
1724
1772
  .command('record [playwrightArgs...]')
1725
1773
  .description('Record videos using Playwright')
1726
1774
  .option('-v, --verbose', 'verbose output')
1727
- .option('--poll-auth', 'wait for sign-in to complete (polling every 5s, up to 5 minutes) instead of exiting, then continue recording')
1775
+ .option('--poll-auth', 'wait for sign-in before recording (now the default off-CI; kept for backwards compatibility)')
1776
+ .option('--no-poll-auth', 'do not wait for sign-in: print the link and exit cleanly if not signed in (the default under CI)')
1728
1777
  .option('--remote', 'trigger the GitHub Actions recording workflow for this project remotely instead of recording locally')
1729
1778
  .option('--languages <langs>', 'record/render only these languages (comma-separated, e.g. fi,en)')
1730
1779
  .allowUnknownOption(true)
@@ -1740,7 +1789,7 @@ export async function main() {
1740
1789
  }
1741
1790
  let playwrightFailure = null;
1742
1791
  try {
1743
- await run('record', parsed.otherArgs, parsed.configPath, parsed.verbose, false, parsed.pollAuth, parsed.languages);
1792
+ await run('record', parsed.otherArgs, parsed.configPath, parsed.verbose, false, parsed.pollAuth, parsed.languages, parsed.noPollAuth);
1744
1793
  }
1745
1794
  catch (error) {
1746
1795
  if (!(error instanceof Error))
@@ -1977,6 +2026,7 @@ export function parseRecordCliArgs(args) {
1977
2026
  let configPath;
1978
2027
  let verbose = false;
1979
2028
  let pollAuth = false;
2029
+ let noPollAuth = false;
1980
2030
  let remote = false;
1981
2031
  let languages;
1982
2032
  const otherArgs = [];
@@ -2013,6 +2063,9 @@ export function parseRecordCliArgs(args) {
2013
2063
  else if (arg === '--poll-auth') {
2014
2064
  pollAuth = true;
2015
2065
  }
2066
+ else if (arg === '--no-poll-auth') {
2067
+ noPollAuth = true;
2068
+ }
2016
2069
  else if (arg === '--remote') {
2017
2070
  remote = true;
2018
2071
  }
@@ -2024,6 +2077,7 @@ export function parseRecordCliArgs(args) {
2024
2077
  configPath,
2025
2078
  verbose,
2026
2079
  pollAuth,
2080
+ noPollAuth,
2027
2081
  remote,
2028
2082
  languages,
2029
2083
  otherArgs,
@@ -2154,7 +2208,7 @@ async function fetchRecordOptionsEnv(configPath, verbose) {
2154
2208
  return {};
2155
2209
  }
2156
2210
  }
2157
- async function run(command, additionalArgs, customConfigPath, verbose = false, mockRecord = false, pollAuth = false, languages) {
2211
+ async function run(command, additionalArgs, customConfigPath, verbose = false, mockRecord = false, pollAuth = false, languages, noPollAuth = false) {
2158
2212
  const configPath = resolveScreenCIConfigPathOrExit(customConfigPath);
2159
2213
  if (command === 'test' || process.env.SCREENCI_RECORDING !== 'true') {
2160
2214
  await loadEnvFileFromConfigSource(configPath, false);
@@ -2165,6 +2219,7 @@ async function run(command, additionalArgs, customConfigPath, verbose = false, m
2165
2219
  await requireScreenCISecret(configPath, {
2166
2220
  interactive: detectInteractiveSession(),
2167
2221
  pollAuth,
2222
+ noPollAuth,
2168
2223
  });
2169
2224
  }
2170
2225
  validateArgs(additionalArgs);