@specsage/cli 0.1.7 → 0.1.9

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/lib/browser.js CHANGED
@@ -248,6 +248,7 @@ async function enumerateElements() {
248
248
  w: Math.round(box.width),
249
249
  h: Math.round(box.height)
250
250
  },
251
+ locator: el,
251
252
  mechanism: 'native',
252
253
  source: 'native'
253
254
  });
@@ -336,6 +337,7 @@ async function enumerateElements() {
336
337
  w: Math.round(box.width),
337
338
  h: Math.round(box.height)
338
339
  },
340
+ locator: el,
339
341
  mechanism: 'scripted',
340
342
  source: source
341
343
  });
@@ -427,7 +429,8 @@ async function enumerateElements() {
427
429
  }
428
430
 
429
431
  lastElements = uniqueElements;
430
- return lastElements;
432
+ // Return elements without the locator property (not JSON-serializable)
433
+ return lastElements.map(({ locator, ...rest }) => rest);
431
434
  }
432
435
 
433
436
  async function debugOverlay(x, y) {
@@ -499,9 +502,26 @@ async function handleCommand(msg) {
499
502
  if (!element) throw new Error(`Element not found: ${element_id}`);
500
503
  lastClickedElement = element; // Store for keypress context
501
504
 
502
- const { x, y, w, h } = element.bounding_box;
505
+ let { x, y, w, h } = element.bounding_box;
506
+
507
+ // Auto-scroll element into view if its center is outside the viewport
508
+ const viewportSize = page.viewportSize();
509
+ let centerY = y + h / 2;
510
+ if (element.locator && (centerY < 0 || centerY >= viewportSize.height)) {
511
+ await element.locator.scrollIntoViewIfNeeded({ timeout: 3000 });
512
+ await new Promise(r => setTimeout(r, 200));
513
+ const newBox = await element.locator.boundingBox();
514
+ if (newBox) {
515
+ x = Math.round(newBox.x);
516
+ y = Math.round(newBox.y);
517
+ w = Math.round(newBox.width);
518
+ h = Math.round(newBox.height);
519
+ element.bounding_box = { x, y, w, h };
520
+ }
521
+ }
522
+
503
523
  const centerX = x + w / 2;
504
- const centerY = y + h / 2;
524
+ centerY = y + h / 2;
505
525
 
506
526
  await debugOverlay(centerX, centerY);
507
527
 
@@ -559,9 +579,26 @@ async function handleCommand(msg) {
559
579
  if (!element) throw new Error(`Element not found: ${element_id}`);
560
580
 
561
581
  // Find the select element by its bounding box center
562
- const { x, y, w, h } = element.bounding_box;
582
+ let { x, y, w, h } = element.bounding_box;
583
+
584
+ // Auto-scroll element into view if its center is outside the viewport
585
+ const viewportSize = page.viewportSize();
586
+ let centerY = y + h / 2;
587
+ if (element.locator && (centerY < 0 || centerY >= viewportSize.height)) {
588
+ await element.locator.scrollIntoViewIfNeeded({ timeout: 3000 });
589
+ await new Promise(r => setTimeout(r, 200));
590
+ const newBox = await element.locator.boundingBox();
591
+ if (newBox) {
592
+ x = Math.round(newBox.x);
593
+ y = Math.round(newBox.y);
594
+ w = Math.round(newBox.width);
595
+ h = Math.round(newBox.height);
596
+ element.bounding_box = { x, y, w, h };
597
+ }
598
+ }
599
+
563
600
  const centerX = x + w / 2;
564
- const centerY = y + h / 2;
601
+ centerY = y + h / 2;
565
602
 
566
603
  // Use Playwright's selectOption by locating the element at the position
567
604
  const selectEl = page.locator('select').filter({
package/lib/cli.rb CHANGED
@@ -71,6 +71,15 @@ class SpecSageCLI
71
71
  parser.parse!(@args)
72
72
  end
73
73
 
74
+ def build_ci_metadata
75
+ metadata = {}
76
+ metadata[:commit_sha] = ENV['GITHUB_SHA'] if ENV['GITHUB_SHA']
77
+ metadata[:branch] = ENV['GITHUB_REF_NAME'] if ENV['GITHUB_REF_NAME']
78
+ metadata[:pr_number] = ENV['GITHUB_PR_NUMBER'].to_i if ENV['GITHUB_PR_NUMBER']
79
+ metadata[:commit_message] = ENV['GITHUB_COMMIT_MESSAGE'] if ENV['GITHUB_COMMIT_MESSAGE']
80
+ metadata
81
+ end
82
+
74
83
  def run_ci_mode
75
84
  website = ENV['TARGET_WEBSITE_SLUG']
76
85
  api_key = ENV['SPEC_SAGE_API_KEY']
@@ -108,7 +117,7 @@ class SpecSageCLI
108
117
  puts ""
109
118
 
110
119
  begin
111
- run_response = publisher.create_ci_run(website)
120
+ run_response = publisher.create_ci_run(website, ci_metadata: build_ci_metadata)
112
121
  server_run_id = run_response['server_run_id']
113
122
  rescue ResultsUploader::UploadError => e
114
123
  puts "Error creating run: #{e.message}"
@@ -58,8 +58,11 @@ class ResultsUploader
58
58
 
59
59
  # Create a CI run on the server
60
60
  # Returns hash with server_run_id and base_url
61
- def create_ci_run(website_identifier)
62
- post("/api/v1/ci/runs", { website: website_identifier })
61
+ # ci_metadata can include: commit_sha, branch, pr_number, commit_message
62
+ def create_ci_run(website_identifier, ci_metadata: {})
63
+ body = { website: website_identifier }
64
+ body.merge!(ci_metadata) if ci_metadata.any?
65
+ post("/api/v1/ci/runs", body)
63
66
  end
64
67
 
65
68
  private
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specsage/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "SpecSage CLI - AI-powered end-to-end testing automation (Node wrapper for Ruby CLI)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,6 +31,6 @@
31
31
  "node": ">=18.0.0"
32
32
  },
33
33
  "dependencies": {
34
- "playwright": "^1.57.0"
34
+ "playwright": "1.57.0"
35
35
  }
36
36
  }