autokap 1.5.4 → 1.5.7

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.
@@ -142,8 +142,20 @@ function findNodeByFuzzyText(tree, description) {
142
142
  return null;
143
143
  }
144
144
  function buildSelectorFromNode(node) {
145
+ if (isUsableSourceRefSelector(node.sourceRef)) {
146
+ return node.sourceRef;
147
+ }
145
148
  // Try to build the most specific selector from node attributes
146
149
  const ref = node.sourceRef;
150
+ // Extract data-ak / data-testid-style automation hooks
151
+ for (const attr of ['data-ak', 'data-ak-interact', 'data-testid', 'data-test', 'data-qa', 'data-cy']) {
152
+ const attrMatch = ref.match(new RegExp(`${attr}="([^"]+)"`));
153
+ if (attrMatch)
154
+ return `[${attr}="${escapeText(attrMatch[1])}"]`;
155
+ const value = node.attributes[attr];
156
+ if (value)
157
+ return `[${attr}="${escapeText(value)}"]`;
158
+ }
147
159
  // Extract data-testid
148
160
  const testIdMatch = ref.match(/data-testid="([^"]+)"/);
149
161
  if (testIdMatch)
@@ -159,11 +171,20 @@ function buildSelectorFromNode(node) {
159
171
  if (role) {
160
172
  return `${tag}[role="${role}"]`;
161
173
  }
162
- // Fallback: use AKNode id as a CSS selector via data attribute
163
- if (node.id) {
164
- return `[data-ak-id="${node.id}"]`;
174
+ if (node.label) {
175
+ return `${tag}:has-text("${escapeText(node.label.slice(0, 50))}")`;
165
176
  }
166
- return `${tag}:has-text("${escapeText(node.label.slice(0, 50))}")`;
177
+ return null;
178
+ }
179
+ function isUsableSourceRefSelector(sourceRef) {
180
+ const ref = (sourceRef ?? '').trim();
181
+ if (!ref || ref.startsWith('<'))
182
+ return false;
183
+ return ref === 'body'
184
+ || ref.startsWith('#')
185
+ || ref.startsWith('.')
186
+ || ref.startsWith('[')
187
+ || /^[a-z][a-z0-9-]*(?:[#.[\s>:]|$)/i.test(ref);
167
188
  }
168
189
  function tokenize(text) {
169
190
  return text
@@ -17,6 +17,7 @@ export async function smartWaitForStability(adapter, options) {
17
17
  const maxWait = options?.maxWaitMs ?? 10000;
18
18
  const startTime = Date.now();
19
19
  const waitedFor = [];
20
+ const unresolvedLoaders = [];
20
21
  // 1. Wait for no visible spinners/skeletons
21
22
  const spinnerSelectors = [
22
23
  '[class*="spinner"]',
@@ -57,6 +58,14 @@ export async function smartWaitForStability(adapter, options) {
57
58
  await sleep(300);
58
59
  }
59
60
  }
61
+ const stillVisible = await adapter.waitFor({
62
+ selector,
63
+ state: 'visible',
64
+ timeoutMs: 100,
65
+ }).catch(() => false);
66
+ if (stillVisible) {
67
+ unresolvedLoaders.push(selector);
68
+ }
60
69
  }
61
70
  }
62
71
  catch {
@@ -70,8 +79,8 @@ export async function smartWaitForStability(adapter, options) {
70
79
  waitedFor.push('stability-pause');
71
80
  }
72
81
  return {
73
- stable: true,
74
- waitedFor,
82
+ stable: unresolvedLoaders.length === 0,
83
+ waitedFor: unresolvedLoaders.length > 0 ? unresolvedLoaders : waitedFor,
75
84
  waitedMs: Date.now() - startTime,
76
85
  };
77
86
  }
@@ -634,6 +634,16 @@ export declare const VideoIngestPayloadSchema: z.ZodObject<{
634
634
  captureId: z.ZodOptional<z.ZodString>;
635
635
  captureName: z.ZodOptional<z.ZodString>;
636
636
  elementSelector: z.ZodOptional<z.ZodString>;
637
+ outscale: z.ZodOptional<z.ZodObject<{
638
+ padding: z.ZodOptional<z.ZodNumber>;
639
+ paddingTop: z.ZodOptional<z.ZodNumber>;
640
+ paddingRight: z.ZodOptional<z.ZodNumber>;
641
+ paddingBottom: z.ZodOptional<z.ZodNumber>;
642
+ paddingLeft: z.ZodOptional<z.ZodNumber>;
643
+ paddingPercent: z.ZodOptional<z.ZodNumber>;
644
+ clampToViewport: z.ZodOptional<z.ZodBoolean>;
645
+ backgroundColor: z.ZodOptional<z.ZodString>;
646
+ }, z.core.$strict>>;
637
647
  description: z.ZodString;
638
648
  postcondition: z.ZodObject<{
639
649
  type: z.ZodEnum<{
@@ -5,7 +5,7 @@
5
5
  * This is the first (and for now only) RuntimeAdapter implementation.
6
6
  */
7
7
  import type { Browser } from './browser.js';
8
- import type { AKTree, VideoPageSignals } from './types.js';
8
+ import type { AKTree, OutscaleConfig, VideoPageSignals } from './types.js';
9
9
  import type { RuntimeAdapter, ClickOptions, WaitCondition, RecordingOptions, RecordingResult, SemanticTarget } from './execution-types.js';
10
10
  import { type ResolveOptions } from './semantic-resolver.js';
11
11
  export declare class WebPlaywrightLocal implements RuntimeAdapter {
@@ -63,7 +63,7 @@ export declare class WebPlaywrightLocal implements RuntimeAdapter {
63
63
  method: string | null;
64
64
  }>;
65
65
  takeScreenshot(): Promise<Buffer>;
66
- takeElementScreenshot(selector: string): Promise<Buffer>;
66
+ takeElementScreenshot(selector: string, outscale?: OutscaleConfig): Promise<Buffer>;
67
67
  takeCleanScreenshot(): Promise<Buffer>;
68
68
  beginRecording(options: RecordingOptions): Promise<void>;
69
69
  getElementBoundingBox(selector: string): Promise<{
@@ -316,8 +316,8 @@ export class WebPlaywrightLocal {
316
316
  async takeScreenshot() {
317
317
  return this.browser.takeScreenshot();
318
318
  }
319
- async takeElementScreenshot(selector) {
320
- const { buffer } = await this.browser.screenshotBySelector(selector);
319
+ async takeElementScreenshot(selector, outscale) {
320
+ const { buffer } = await this.browser.screenshotBySelector(selector, outscale);
321
321
  return buffer;
322
322
  }
323
323
  async takeCleanScreenshot() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autokap",
3
- "version": "1.5.4",
3
+ "version": "1.5.7",
4
4
  "description": "AI-powered CLI tool for capturing clean screenshots of websites",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",