@playdrop/playdrop-cli 0.14.0 → 0.14.2

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.
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "0.14.0",
2
+ "version": "0.14.2",
3
3
  "build": 1,
4
- "runtimeSdkVersion": "0.14.0",
4
+ "runtimeSdkVersion": "0.14.2",
5
5
  "runtimeSdkBuild": 1,
6
6
  "clients": {
7
7
  "all": {
@@ -30,32 +30,14 @@ export type HostedLaunchState = {
30
30
  errorCode: string | null;
31
31
  message: string | null;
32
32
  };
33
- export type ProjectCheckAction = {
34
- type: 'click';
35
- x: number;
36
- y: number;
37
- button?: 'left' | 'right' | 'middle';
38
- } | {
39
- type: 'press';
40
- key: string;
41
- } | {
42
- type: 'wait';
43
- ms: number;
44
- };
33
+ export declare function formatConsoleValue(value: unknown): string;
34
+ export declare function formatBrowserConsoleMessage(values: unknown[], fallbackText: string): string;
45
35
  export declare function resolveLoadCheckSurface(surfaceTargets: unknown, primarySurface?: unknown): AppSurface;
46
36
  export declare function cloneLoadCheckContextOptions(surface: AppSurface): BrowserContextOptions;
47
37
  export declare function isSoftwareWebGlRenderer(renderer: string | null | undefined): boolean;
48
- export declare function normalizeProjectCheckActions(value: unknown): ProjectCheckAction[];
49
- export declare function focusProjectCheckGameFrame(page: Pick<Page, 'mouse'>, frame: Frame): Promise<{
50
- x: number;
51
- y: number;
52
- width: number;
53
- height: number;
54
- }>;
55
38
  export declare function dispatchPlaytestTapeToFrame(page: Page, frame: Frame, surface: AppSurface, tape: AppPlaytestTape, hooks?: {
56
39
  onBeforeEvent?: (eventIndex: number, event: AppPlaytestTapeEvent) => void;
57
40
  }): Promise<void>;
58
- export declare function dispatchProjectCheckActionsToFrame(page: Page, frame: Frame, actions: readonly ProjectCheckAction[]): Promise<void>;
59
41
  export declare function formatHostedLoadCheckFailure(taskName: string, result: HostedLoadCheckResult, scope?: 'local' | 'staged' | 'final'): string;
60
42
  export declare function redactHostedLoadCheckSecrets(text: string, sourceUrl: string): string;
61
43
  export declare function runLocalHostedLoadCheck(input: {
@@ -71,7 +53,6 @@ export declare function runLocalHostedLoadCheck(input: {
71
53
  captureSession?: TaskCaptureSession | null;
72
54
  allowUnregisteredViewerLaunch?: boolean;
73
55
  screenshotPath?: string | null;
74
- actions?: ProjectCheckAction[];
75
56
  surface?: AppSurface;
76
57
  playtestTape?: AppPlaytestTape;
77
58
  postReadyWaitMs?: number;
@@ -4,13 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.PLAYDROP_SURFACE_CONTEXT_OPTIONS = void 0;
7
+ exports.formatConsoleValue = formatConsoleValue;
8
+ exports.formatBrowserConsoleMessage = formatBrowserConsoleMessage;
7
9
  exports.resolveLoadCheckSurface = resolveLoadCheckSurface;
8
10
  exports.cloneLoadCheckContextOptions = cloneLoadCheckContextOptions;
9
11
  exports.isSoftwareWebGlRenderer = isSoftwareWebGlRenderer;
10
- exports.normalizeProjectCheckActions = normalizeProjectCheckActions;
11
- exports.focusProjectCheckGameFrame = focusProjectCheckGameFrame;
12
12
  exports.dispatchPlaytestTapeToFrame = dispatchPlaytestTapeToFrame;
13
- exports.dispatchProjectCheckActionsToFrame = dispatchProjectCheckActionsToFrame;
14
13
  exports.formatHostedLoadCheckFailure = formatHostedLoadCheckFailure;
15
14
  exports.redactHostedLoadCheckSecrets = redactHostedLoadCheckSecrets;
16
15
  exports.runLocalHostedLoadCheck = runLocalHostedLoadCheck;
@@ -37,7 +36,6 @@ var surfaceProfiles_2 = require("./surfaceProfiles");
37
36
  Object.defineProperty(exports, "PLAYDROP_SURFACE_CONTEXT_OPTIONS", { enumerable: true, get: function () { return surfaceProfiles_2.PLAYDROP_SURFACE_CONTEXT_OPTIONS; } });
38
37
  const FRAME_SELECTOR = 'iframe[title="Game"]';
39
38
  const DEFAULT_HOSTED_LOAD_TIMEOUT_MS = 15000;
40
- const POST_READY_SETTLE_MS = 750;
41
39
  const PLAYTEST_INTERACTIVE_MEAN_DELTA_MIN = 0.002;
42
40
  const PLAYTEST_INTERACTIVE_CHANGED_PIXEL_RATIO_MIN = 0.01;
43
41
  const LOAD_CHECK_SURFACE_ORDER = ['DESKTOP', 'MOBILE_LANDSCAPE', 'MOBILE_PORTRAIT'];
@@ -75,6 +73,9 @@ function isKnownGoogleTelemetryUrl(rawUrl) {
75
73
  return GOOGLE_TELEMETRY_HOSTS.some((domain) => isHostOrSubdomain(hostname, domain));
76
74
  }
77
75
  function formatConsoleValue(value) {
76
+ if (value instanceof Error) {
77
+ return `${value.name}: ${value.message}`;
78
+ }
78
79
  if (typeof value === 'string')
79
80
  return value;
80
81
  if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
@@ -84,6 +85,10 @@ function formatConsoleValue(value) {
84
85
  return 'undefined';
85
86
  }
86
87
  if (typeof value === 'object') {
88
+ const namedError = value;
89
+ if (typeof namedError.message === 'string') {
90
+ return `${typeof namedError.name === 'string' ? `${namedError.name}: ` : ''}${namedError.message}`;
91
+ }
87
92
  try {
88
93
  return JSON.stringify(value);
89
94
  }
@@ -93,6 +98,13 @@ function formatConsoleValue(value) {
93
98
  }
94
99
  return String(value);
95
100
  }
101
+ function formatBrowserConsoleMessage(values, fallbackText) {
102
+ if (values.length === 0) {
103
+ return fallbackText;
104
+ }
105
+ const rendered = values.map(formatConsoleValue).join(' ');
106
+ return rendered === '{}' || rendered === '[object]' ? fallbackText : rendered;
107
+ }
96
108
  function serializePayload(payload) {
97
109
  if (payload === undefined) {
98
110
  return '';
@@ -263,67 +275,6 @@ function isSoftwareWebGlRenderer(renderer) {
263
275
  const normalized = typeof renderer === 'string' ? renderer.trim() : '';
264
276
  return normalized.length > 0 && SOFTWARE_WEBGL_RENDERER_PATTERNS.some((pattern) => pattern.test(normalized));
265
277
  }
266
- function normalizeProjectCheckActions(value) {
267
- if (value === undefined || value === null) {
268
- return [];
269
- }
270
- if (!Array.isArray(value)) {
271
- throw new Error('project_check_actions_invalid: expected a JSON array.');
272
- }
273
- return value.map((entry, index) => {
274
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
275
- throw new Error(`project_check_action_invalid:${index}`);
276
- }
277
- const type = typeof entry.type === 'string'
278
- ? entry.type.trim().toLowerCase()
279
- : '';
280
- if (type === 'click') {
281
- const x = Number(entry.x);
282
- const y = Number(entry.y);
283
- if (!Number.isFinite(x) || !Number.isFinite(y)) {
284
- throw new Error(`project_check_action_invalid:${index}:click_coordinates_required`);
285
- }
286
- const rawButton = typeof entry.button === 'string'
287
- ? entry.button.trim().toLowerCase()
288
- : '';
289
- const button = rawButton === 'right' || rawButton === 'middle' ? rawButton : 'left';
290
- return { type: 'click', x, y, button };
291
- }
292
- if (type === 'press') {
293
- const key = typeof entry.key === 'string'
294
- ? entry.key.trim()
295
- : '';
296
- if (!key) {
297
- throw new Error(`project_check_action_invalid:${index}:key_required`);
298
- }
299
- return { type: 'press', key };
300
- }
301
- if (type === 'wait') {
302
- const ms = Number(entry.ms);
303
- if (!Number.isInteger(ms) || ms < 0 || ms > 60000) {
304
- throw new Error(`project_check_action_invalid:${index}:wait_ms_invalid`);
305
- }
306
- return { type: 'wait', ms };
307
- }
308
- throw new Error(`project_check_action_type_unknown:${index}`);
309
- });
310
- }
311
- async function focusProjectCheckGameFrame(page, frame) {
312
- const element = await frame.frameElement();
313
- try {
314
- await element.scrollIntoViewIfNeeded().catch(() => { });
315
- const box = await element.boundingBox();
316
- if (!box) {
317
- throw new Error('project_check_game_frame_not_visible');
318
- }
319
- await page.mouse.click(box.x + (box.width / 2), box.y + (box.height / 2), { button: 'left' });
320
- await frame.evaluate(() => window.focus()).catch(() => { });
321
- return box;
322
- }
323
- finally {
324
- await element.dispose().catch(() => { });
325
- }
326
- }
327
278
  async function prepareProjectCheckGameFrame(frame) {
328
279
  const element = await frame.frameElement();
329
280
  try {
@@ -457,24 +408,6 @@ async function dispatchPlaytestTapeToFrame(page, frame, surface, tape, hooks = {
457
408
  }
458
409
  }
459
410
  }
460
- async function dispatchProjectCheckActionsToFrame(page, frame, actions) {
461
- for (const action of actions) {
462
- if (action.type === 'wait') {
463
- await page.waitForTimeout(action.ms);
464
- continue;
465
- }
466
- const box = await focusProjectCheckGameFrame(page, frame);
467
- if (action.type === 'click') {
468
- await page.mouse.click(box.x + action.x, box.y + action.y, { button: action.button ?? 'left' });
469
- continue;
470
- }
471
- if (action.type === 'press') {
472
- await page.keyboard.press(action.key);
473
- continue;
474
- }
475
- throw new Error('project_check_action_type_unknown');
476
- }
477
- }
478
411
  async function findGameFrame(page, timeoutMs) {
479
412
  const handle = await page.waitForSelector(FRAME_SELECTOR, { timeout: timeoutMs });
480
413
  const frame = await handle.contentFrame();
@@ -514,7 +447,6 @@ async function saveScreenshot(page, screenshotPath) {
514
447
  }
515
448
  await (0, promises_1.mkdir)((0, node_path_1.dirname)(normalizedPath), { recursive: true });
516
449
  await page.screenshot({ path: normalizedPath, fullPage: true });
517
- console.log(`[check] Saved screenshot to ${(0, node_path_1.relative)(process.cwd(), normalizedPath) || normalizedPath}`);
518
450
  return normalizedPath;
519
451
  }
520
452
  async function saveGameFrameScreenshot(page, screenshotPath) {
@@ -637,9 +569,11 @@ async function runHostedLoadCheck(options) {
637
569
  settleHostedLaunchWaiter(normalizedHostedState);
638
570
  }
639
571
  }
640
- const serialized = serializePayload(payload);
641
- const line = redactReportText(['[check][custom]', typeof type === 'string' ? type.toLowerCase() : 'info', serialized].filter(Boolean).join(' '));
642
- console.log(line);
572
+ const normalizedType = typeof type === 'string' ? type.toLowerCase() : 'info';
573
+ if (/error|failed|denied|unreachable/.test(normalizedType)) {
574
+ const serialized = serializePayload(payload);
575
+ errors.push(redactReportText(['[check][custom]', normalizedType, serialized].filter(Boolean).join(' ')));
576
+ }
643
577
  });
644
578
  await page.addInitScript(() => {
645
579
  window.addEventListener('unhandledrejection', (event) => {
@@ -676,14 +610,11 @@ async function runHostedLoadCheck(options) {
676
610
  const handleConsoleMessage = async (message) => {
677
611
  const type = message.type();
678
612
  const args = await Promise.all(message.args().map((arg) => arg.jsonValue().catch(() => arg.toString())));
679
- const rendered = args.length > 0 ? args.map(formatConsoleValue).join(' ') : message.text();
613
+ const rendered = formatBrowserConsoleMessage(args, message.text());
680
614
  const line = redactReportText(withTapeAction(`[check][console:${type}] ${rendered}`));
681
615
  if (type === 'error' || type === 'assert' || type === 'trace') {
682
616
  errors.push(line);
683
- console.error(line);
684
- return;
685
617
  }
686
- console.log(line);
687
618
  };
688
619
  page.on('console', (message) => {
689
620
  void handleConsoleMessage(message).catch((error) => {
@@ -695,7 +626,6 @@ async function runHostedLoadCheck(options) {
695
626
  const text = redactReportText(error?.message ?? String(error));
696
627
  const line = withTapeAction(`[check][pageerror] ${text}`);
697
628
  errors.push(line);
698
- console.error(line);
699
629
  });
700
630
  page.on('requestfailed', (request) => {
701
631
  const failure = request.failure();
@@ -710,7 +640,6 @@ async function runHostedLoadCheck(options) {
710
640
  }
711
641
  const line = withTapeAction(`[check][requestfailed] ${text}`);
712
642
  errors.push(line);
713
- console.error(line);
714
643
  });
715
644
  page.on('response', (response) => {
716
645
  const status = response.status();
@@ -724,9 +653,7 @@ async function runHostedLoadCheck(options) {
724
653
  }
725
654
  const line = withTapeAction(`[check][response] ${text}`);
726
655
  errors.push(line);
727
- console.error(line);
728
656
  });
729
- console.log(`[check] Opening ${reportTargetUrl}`);
730
657
  const response = await page.goto(options.targetUrl, { waitUntil: 'domcontentloaded', timeout: 30000 });
731
658
  if (response && !response.ok()) {
732
659
  errors.push(`[check][navigation] ${response.status()} ${response.statusText()} ${reportTargetUrl}`);
@@ -764,14 +691,10 @@ async function runHostedLoadCheck(options) {
764
691
  }
765
692
  const frame = await findGameFrame(page, readinessTimeoutMs);
766
693
  webglRenderer = await readWebGlRenderer(frame);
767
- console.log(`[check] WebGL renderer: ${webglRenderer}`);
768
694
  if (isSoftwareWebGlRenderer(webglRenderer)) {
769
695
  throw new Error(`project_check_webgl_renderer_software: WebGL renderer is "${webglRenderer}". Real hardware acceleration is required.`);
770
696
  }
771
697
  await saveGameFrameScreenshot(page, options.readyGameFrameScreenshotPath);
772
- if (options.playtestTape && options.actions?.length) {
773
- throw new Error('project_check_input_mode_conflict: playtest tape and legacy actions cannot run together.');
774
- }
775
698
  if (options.playtestTape) {
776
699
  if (!options.playtestSurface) {
777
700
  throw new Error('project_check_playtest_surface_required');
@@ -782,10 +705,6 @@ async function runHostedLoadCheck(options) {
782
705
  },
783
706
  });
784
707
  }
785
- else if (options.actions?.length) {
786
- await dispatchProjectCheckActionsToFrame(page, frame, options.actions);
787
- await page.waitForTimeout(POST_READY_SETTLE_MS);
788
- }
789
708
  else if (options.postReadyWaitMs && options.postReadyWaitMs > 0) {
790
709
  await page.waitForTimeout(options.postReadyWaitMs);
791
710
  }
@@ -916,7 +835,6 @@ async function runLocalHostedLoadCheck(input) {
916
835
  expectedHostedLaunchState: resolvePostAuthHostedLaunchState(localAppMetadata.controllerMode),
917
836
  contextOptions,
918
837
  screenshotPath: input.screenshotPath,
919
- actions: input.actions,
920
838
  playtestTape: input.playtestTape,
921
839
  playtestSurface: captureSurface,
922
840
  postReadyWaitMs: input.postReadyWaitMs,
@@ -962,7 +880,6 @@ async function runLocalHostedLoadCheck(input) {
962
880
  savedSessionBootstrap: true,
963
881
  contextOptions,
964
882
  screenshotPath: input.screenshotPath,
965
- actions: input.actions,
966
883
  playtestTape: input.playtestTape,
967
884
  playtestSurface: captureSurface,
968
885
  postReadyWaitMs: input.postReadyWaitMs,
@@ -981,7 +898,6 @@ async function runLocalHostedLoadCheck(input) {
981
898
  expectedHostedLaunchState: anonymousExpectedState,
982
899
  contextOptions,
983
900
  screenshotPath: input.screenshotPath,
984
- actions: input.actions,
985
901
  playtestTape: input.playtestTape,
986
902
  playtestSurface: captureSurface,
987
903
  postReadyWaitMs: input.postReadyWaitMs,
@@ -1,5 +1,6 @@
1
1
  import type { AppSurface } from '@playdrop/types';
2
2
  import type { BrowserContextOptions } from 'playwright-core';
3
+ export declare const PLAYDROP_MOBILE_USER_AGENT = "Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1";
3
4
  export type PlaydropSurfaceProfile = {
4
5
  contextOptions: BrowserContextOptions;
5
6
  playwrightMcpDevice: string | null;
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PLAYDROP_SURFACE_CONTEXT_OPTIONS = exports.PLAYDROP_SURFACE_PROFILES = void 0;
3
+ exports.PLAYDROP_SURFACE_CONTEXT_OPTIONS = exports.PLAYDROP_SURFACE_PROFILES = exports.PLAYDROP_MOBILE_USER_AGENT = void 0;
4
4
  exports.clonePlaydropSurfaceContextOptions = clonePlaydropSurfaceContextOptions;
5
5
  exports.buildPlaywrightMcpSurfaceArgs = buildPlaywrightMcpSurfaceArgs;
6
- const MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1';
6
+ exports.PLAYDROP_MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 26_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1';
7
7
  exports.PLAYDROP_SURFACE_PROFILES = {
8
8
  DESKTOP: {
9
9
  contextOptions: {
@@ -21,7 +21,7 @@ exports.PLAYDROP_SURFACE_PROFILES = {
21
21
  deviceScaleFactor: 3,
22
22
  isMobile: true,
23
23
  hasTouch: true,
24
- userAgent: MOBILE_USER_AGENT,
24
+ userAgent: exports.PLAYDROP_MOBILE_USER_AGENT,
25
25
  },
26
26
  playwrightMcpDevice: 'iPhone 13 landscape',
27
27
  },
@@ -32,7 +32,7 @@ exports.PLAYDROP_SURFACE_PROFILES = {
32
32
  deviceScaleFactor: 3,
33
33
  isMobile: true,
34
34
  hasTouch: true,
35
- userAgent: MOBILE_USER_AGENT,
35
+ userAgent: exports.PLAYDROP_MOBILE_USER_AGENT,
36
36
  },
37
37
  playwrightMcpDevice: 'iPhone 13',
38
38
  },
@@ -60,5 +60,6 @@ function buildPlaywrightMcpSurfaceArgs(surface) {
60
60
  ...(profile.playwrightMcpDevice ? ['--device', profile.playwrightMcpDevice] : []),
61
61
  '--viewport-size',
62
62
  `${viewport.width}x${viewport.height}`,
63
+ ...(profile.contextOptions.userAgent ? ['--user-agent', profile.contextOptions.userAgent] : []),
63
64
  ];
64
65
  }
@@ -1,5 +1,5 @@
1
1
  import type { AppTask } from '../catalogue';
2
2
  export type AppValidationWarningMode = 'source' | 'bundle';
3
- export declare function collectAppValidationWarnings(task: AppTask, mode?: AppValidationWarningMode): string[];
3
+ export declare function collectAppValidationWarnings(task: AppTask, _mode?: AppValidationWarningMode): string[];
4
4
  export declare function runFormatScript(task: AppTask): Promise<boolean>;
5
5
  export declare function validateAppTask(task: AppTask): Promise<void>;