Skip to content

sh1pt browser: Playwright recipes for the consoles that have no API - #1008

Merged
ralyodio merged 2 commits into
masterfrom
feat/browser-automation
Sep 6, 2026
Merged

ralyodio merged 2 commits into
masterfrom
feat/browser-automation

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Why

Some provider settings have no CLI and no API. Google's OAuth consent screen is the standard case: gcloud covers the rest of Google Cloud, but test users, publishing status and a client's redirect URIs are console pages. An app left in Testing with no test users answers every sign-in with Error 403: access_denied, and nothing on the command line can change that.

What

  • packages/automation/browser (@profullstack/sh1pt-automation-browser): typed, reusable Playwright recipes on a persistent Chrome profile, so a sign-in is answered once and every later run is unattended. When only a person can answer (a 2FA code, a captcha), session.ask() writes a screenshot and question into the run's artifacts and waits for an answer file, so an unattended box parks instead of failing.
  • First recipe google-cloud-oauth: status, add-test-users, publish, add-redirect-uri.
  • sh1pt browser command: list plus <recipe> <action>. Playwright is an optional peer dependency, loaded lazily, so machines without it run every other command fine.
  • Docs: a "When there is no CLI and no API" section in CLI_INTEGRATIONS.md and a package README.

Three things that decide whether a sign-in works

Learned the hard way against Google, all in session.ts:

  1. Playwright's headless: true asks for the old headless binary, which sign-in pages recognise and refuse. Drive a real Chrome and pass --headless=new.
  2. New headless still reports HeadlessChrome; Google answers that with a stripped flow (flowName=WebLiteSignIn) that will not take a password at all. Send a desktop user agent built from the binary's own version.
  3. With no authenticator a passkey prompt never settles: the page sits on "Verifying it's you…" and even its own "Try another way" button does nothing, because the flow is still waiting. Registering Chrome's DevTools virtual authenticator makes the request fail like an empty security key, and the password fallback appears.

Verified

  • Full suite the way CI runs it (build the library packages, then pnpm test): 719 files, 3,642 tests, 0 failures. Without that build step 670 files fail to collect on @profullstack/sh1pt-core/testing; that is the known unbuilt-worktree behaviour, not this change.
  • tsc --noEmit clean for the new package and the CLI.
  • Live against Google: the recipe now walks identifier → passkey → method chooser → password reliably. It stops at the password itself, which the stored credential no longer satisfies; the recipe reports that instead of looping.
  • add-test-users / publish are therefore not yet exercised end to end; the page objects are written from the live DOM but wait on a working credential.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WYMJH7N4d2qRct5Q5q2YWQ

Some provider settings have no CLI and no API. Google's OAuth consent
screen is the standard case: gcloud covers the rest of Google Cloud, but
test users, publishing status and a client's redirect URIs are console
pages, and an app left in Testing with no test users answers every
sign-in with Error 403: access_denied.

packages/automation/browser holds those as typed, reusable recipes on a
persistent Chrome profile, so a sign-in is answered once and every later
run is unattended. `sh1pt browser list` prints the shelf.

Three findings decide whether a sign-in works at all, and all three are
in session.ts:

- Playwright's headless:true asks for the old headless binary, which
  sign-in pages recognise and refuse. Drive a real Chrome and pass
  --headless=new instead.
- New headless still reports HeadlessChrome, and Google answers that
  with a stripped flow that will not accept a password. Send a desktop
  user agent built from the binary's own version.
- With no authenticator a passkey prompt never settles: the page sits on
  "Verifying it's you…" and even its own "Try another way" button does
  nothing. Registering Chrome's DevTools virtual authenticator makes the
  request fail like an empty security key, and the password fallback
  appears.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYMJH7N4d2qRct5Q5q2YWQ
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

49 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 14 | LOW: 34

Severity Rule Location
HIGH js-host-header-trust packages/bots/wechat/src/index.ts:405
MEDIUM redos-nested-quantifier packages/actions-fleet-core/src/action-pack/schema.ts:3
MEDIUM redos-nested-quantifier packages/core/src/setup-helpers.ts:583
MEDIUM redos-nested-quantifier packages/policy/src/rules/bundle-id.ts:3
MEDIUM sql-string-concatenation packages/targets/deploy-wordpress/src/index.ts:154
MEDIUM redos-nested-quantifier packages/targets/desktop-linux/src/index.ts:19
MEDIUM redos-nested-quantifier packages/targets/desktop-mac/src/index.ts:15
MEDIUM redos-nested-quantifier packages/targets/desktop-steamos/src/index.ts:28
MEDIUM redos-nested-quantifier packages/targets/mobile-android/src/index.ts:9
MEDIUM redos-nested-quantifier packages/targets/mobile-ios/src/index.ts:11
MEDIUM redos-nested-quantifier packages/targets/tv-androidtv/src/index.ts:14
MEDIUM redos-nested-quantifier packages/targets/tv-firetv/src/index.ts:13
MEDIUM redos-nested-quantifier packages/targets/tv-tvos/src/index.ts:14
MEDIUM redos-nested-quantifier packages/targets/tv-webos/src/index.ts:26
MEDIUM js-unescaped-html-sink sites/sh1pt.com/app/blog/[slug]/page.tsx:76
LOW secret-generic-credential packages/affiliates/skimlinks/src/index.test.ts:25
LOW secret-generic-credential packages/affiliates/skimlinks/src/index.test.ts:71
LOW secret-generic-api-key packages/affiliates/sovrn/src/index.ts:28
LOW secret-generic-credential packages/agent-providers/opencode/src/__tests__/opencode.test.ts:99
LOW js-nosql-injection packages/ai/amazon-bedrock/src/index.test.ts:121
LOW secret-generic-credential packages/ai/amazon-bedrock/src/index.ts:9
LOW secret-generic-credential packages/ai/amazon-bedrock/src/index.ts:10
LOW secret-generic-credential packages/ai/amazon-bedrock/src/index.ts:11
LOW secret-generic-credential packages/bridges/matrix/src/index.ts:58
LOW secret-generic-credential packages/bridges/matrix/src/index.ts:59
LOW secret-generic-credential packages/bridges/slack/src/index.test.ts:259
LOW secret-generic-credential packages/captcha/captchasolver/src/index.ts:34
LOW secret-generic-credential packages/cli/src/commands/secrets.ts:189
LOW secret-generic-credential packages/cloud/linode/src/index.ts:15
LOW secret-generic-credential packages/observability/sentry/src/index.ts:15
LOW secret-generic-credential packages/outreach/producthunt/src/index.ts:103
LOW secret-generic-credential packages/promo/posthog/src/index.ts:23
LOW secret-generic-credential packages/scanners/threatcrush/test/scan-output.txt:35
LOW secret-generic-credential packages/scanners/threatcrush/test/scan-output.txt:40
LOW secret-database-url packages/scanners/threatcrush/test/scan-output.txt:54
LOW secret-generic-credential packages/security/snyk/src/index.ts:26
LOW secret-generic-credential packages/social/hashnode/src/index.ts:4
LOW secret-generic-credential packages/social/linkedin/src/index.ts:3
LOW secret-generic-credential packages/social/linkedin/src/index.ts:4
LOW secret-generic-credential packages/social/medium/src/index.ts:4
LOW secret-generic-credential packages/social/snapchat/src/index.ts:5
LOW secret-generic-credential packages/social/tiktok/src/index.ts:5
LOW secret-generic-credential packages/targets/plugin-vscode/src/index.test.ts:115
LOW secret-generic-credential packages/targets/registry-ans/src/index.test.ts:79
LOW secret-generic-credential packages/targets/registry-ans/src/index.ts:49
LOW secret-generic-credential packages/targets/sdk-pypi/src/index.test.ts:49
LOW secret-generic-credential packages/vcs/gitlab/src/index.test.ts:96
LOW secret-generic-credential sites/sh1pt.com/supabase/config.toml:303
LOW secret-generic-credential sites/sh1pt.com/supabase/config.toml:335

Snippets are redacted; ThreatCrush never prints matched credential material.

The cli now depends on @profullstack/sh1pt-automation-browser, and pnpm
rewrites workspace: to a real range at publish time. Without this the
release would ship a cli whose dependency does not exist on npm, and
every npm i @profullstack/sh1pt would 404 on it. Added to PACKAGES in
scripts/version.mjs and to the build + publish steps in release.yml, in
dependency order, and kept the test file out of the published dist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYMJH7N4d2qRct5Q5q2YWQ
@ralyodio
ralyodio merged commit 40e9069 into master Sep 6, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant