Browser sign-in identities
A browser identity is one persona's whole browser profile: every site that
persona uses, signed in together (for example a Google account, the product
account and a payment provider). A project declares as many identities as its
tests need, under names it chooses. Each identity has its own profile, so
signing a second account in never overwrites the first. Within one profile a
site holds one active login, as in a real browser; a test that needs two
accounts on the same site at once uses two identities. default always exists
and is the project's original single profile, so a project that declares
nothing behaves as before.
Declaring identities
Identities are nonsecret data in the project's browser-control capability
settings. Each site names its origin, optionally the account a human should
use, and a signed-in check: a URL plus either signed_in_selector (the page
shows that element and no sign-in wall) or signed_in_status (the URL,
requested with the profile's cookies and without following redirects, answers
that HTTP status). When a case lands on Google's account chooser, the runner
clicks the account recorded on that identity's accounts.google.com site.
{"identities": {"buyer": {"sites": [
{"site": "google", "origin": "https://accounts.google.com",
"account": "buyer@example.com",
"probe": {"url": "https://myaccount.google.com/", "signed_in_selector": "a[aria-label^='Google Account']"}},
{"site": "shop", "origin": "https://shop.example.com",
"probe": {"url": "https://shop.example.com/api/me", "signed_in_status": 200}}
]}}}
Set it with yoke projects capability-settings set --project P --cap-type browser-control. Credentials outside the browser (CLI tokens, API keys) stay on
the capability-secrets surface; an identity holds only browser sessions.
Checking and signing in
yoke browser verify --project P --identity buyer # buyer: google ok, shop expired
yoke browser authorize --project P --identity buyer # window only for expired sites
verify checks every declared site headless and reports each one. authorize
runs the same check first and returns at once when every site is signed in,
unless you supply --url URL. With --url, it always opens that URL so you can
complete an extra access check such as GitHub Confirm access. This also applies
to --desktop-browser and --test-machine.
Otherwise it signs expired automatic sites in headless, then opens one window
with a tab per expired human site, naming the identity,
the site and the account, and checks again after the window closes. A site
whose check cannot be reached fails by name: no sign-in fixes it.
If default declares no sites while other identities do, omitting --identity
refuses with browser_identity_required and names the choices before any window
opens. Choose the declared identity; site, origin and account instructions print
on the workstation before its viewer opens.
Linux remote authorization streams progress as it runs. JSON mode keeps stdout
for the final receipt and sends progress to stderr.
While a sign-in window is open no automated browser runs on the machine: every
browser daemon is stopped first, and a daemon start is refused with
browser_human_gate_active, naming the identity, project, PID and how long
the window has been open. Hand off to steering or the person at that machine
to finish or close that sign-in window, or quit the named PID, then retry.
Sign-in windows close after 30 minutes; managed Chromium gets 10 seconds to
quit before it is force-stopped. A gate marker older than 30 minutes plus
that shutdown margin is removed even if its recorded PID is still alive.
Automatic sign-in for first-party test accounts
Use a dedicated test account on your own application with synthetic data.
Google, Microsoft and sites requiring MFA stay human: omit sign_in for them.
Add this optional declaration to the site's existing object:
"sign_in": {
"mode": "automatic",
"url": "https://shop.example.com/login",
"steps": [
{"fill": "#email", "secret": "email"},
{"fill": "#password", "secret": "password"},
{"click": "button[type=submit]"}
]
}
The URL must have the site's origin, so the app can create its login state before redirecting. For a hosted identity page, add its exact origin:
"credential_origins": ["https://example.auth.us-east-1.amazoncognito.com"]
Fill steps type only on the app origin or these declared credential origins. Each entry must be a unique HTTPS origin with no path, userinfo, query, fragment or wildcard. After the final step, sign-in waits up to 15 seconds for the browser to return to the app origin; the app's own signed-in probe then must pass. A flow that never returns fails. Selectors and field names are nonsecret; never put values in settings. Store each field once on the machine that runs the Browser case:
yoke projects capability secret set --project P --cap-type browser-control --key sign-in.buyer.shop.email --value-file /private/path/test-email
yoke projects capability secret set --project P --cap-type browser-control --key sign-in.buyer.shop.password --value-stdin
Enter the value privately through stdin or a file; agents must never read it or
put it in a command argument. The fixed key pattern is
sign-in.IDENTITY.SITE.FIELD, with 1โ40 lowercase letters, digits or hyphens
per name. Values live only under this machine's
~/.yoke/secrets/capability-secrets/P/browser-control/, beside its profiles;
they are never sent to the control plane. Configure every runner machine once.
authorize signs expired automatic sites in without opening a window. Before
running checks, sign in once with this command. Every QA check copies the
identity's cookies and storage into its own private profile and daemon. Checks
never stop or write the authoritative profile. When it expires, authorize again;
the next check copies the fresh sign-in state. verify stays
read-only. The managed Playwright browser uses its existing basic password
store and mock Keychain switches, including headless background execution;
it needs no GUI login session or macOS Keychain approval. Sign-in has no
screenshots or tracing, and reports site states without page text or raw errors.
Native Test Machine browser sign-in remains human.
A Browser case can also sign in during its run after sign-out or session
expiry: use {"action": "sign_in", "site": "shop"} with this identity selected.
It uses the daemon's open context and the case's page, then checks the site's
signed-in probe. The step result records only the site, identity and check
outcome. Python passes secret file paths to the daemon; only the sign-in
function reads their values. Never put credentials in authored step values.
A site without automatic sign_in refuses as
browser_sign_in_step_not_automatic; declare it for that identity or keep
human sign-in outside the case.
browser_sign_in_secret_missing names the exact secret setup command.
browser_automatic_sign_in_failed names the site: check the account and steps,
then retry, or remove sign_in and run yoke browser authorize for human
sign-in. A failed automatic sign-in stops the case before it captures anything.
Signing in on a Test Machine
Run from the workstation connected to the project's control plane:
yoke browser authorize --project P --identity buyer --test-machine NAME
Yoke reads the browser-control declarations here and runs authorization in the
registered Test Machine's GUI session over its SSH route. A Linux or Windows
WSL machine with a registered RDP desktop opens in the same FreeRDP viewer as
yoke test-machine desktop-access --view (fixture password on stdin, SSH
forward closed with the viewer). A window titled Yoke desktop: NAME appears.
Sign in with the identity's declared account. Close the browser window when
done; the viewer closes when verification finishes. If no viewer route or
FreeRDP client is available, the command prints the exact desktop-access
command and recovery and still waits for sign-in. A macOS machine with a
registered VNC route opens in Screen Sharing from a Mac workstation: paste the
desktop password from the clipboard, which Yoke clears after 60 seconds
(clipboard-history apps may keep a copy), and quit Screen Sharing after
verification finishes. The Test Machine needs an installed
Yoke browser runtime, but no control-plane connection or pasted settings.
Read yoke browser authorize --help for reset, starting URL, JSON output
and window bounds.
On Linux and macOS, --test-machine NAME checks both automation and native desktop
browser sign-ins. Add --desktop-browser to check only that machine's native
Chromium or Safari. Native sign-ins stay live through resets and are never saved
into golden snapshots or wiped by authorization. A second Linux viewer refuses
linux_desktop_viewer_connected; close the open Yoke desktop: NAME viewer or
let its authorization finish, then retry. Linux authorization uses xdotool
to detect closure of the spawned Chromium PID's windows before verification.
If an approval walk finds a Google or Microsoft sign-in page instead of its exact
request tab, it names machine_browser_sign_in_required. The operator completes
the declared identity's native sign-in before retrying; approval automation never
enters credentials or clicks an unrelated request.
The workstation needs an ssh-agent with a key the host accepts. macOS uses its
logged-in Terminal.app session; Linux and Windows WSL use the registered user's
XFCE desktop. For a desktop that needs to start, its registered desktop_password
secret must be stored on this workstation. A machine leased by another session
refuses: wait for that lease to release or ask its holder to authorize the
identity, because signing in stops every automated browser on that host.
Read desktop access for the viewer route,
FreeRDP client and macOS Screen Sharing recovery.
Using an identity
yoke qa browser setup|status|stop|screenshot|step take --identity NAME
and read no declarations, so they run on a host that cannot reach the
project's control plane. verify and authorize need the identity's sites:
for a registered Test Machine, authorize from the connected workstation with
--test-machine NAME. For hosts without a remote route, run locally there with
--declarations-json, using the browser-control settings document read where
the project is reachable. --test-machine and --declarations-json are exclusive. A
Browser case that needs sign-in names its identity with
method_config.browser_identity. Choose the identity whose account can access
the case's routes and data; an account signed into a different organization
cannot prove a case about your organization. The runner validates named identities
before starting any browser daemon. Public cases omit the field and continue
with a private copy of the default profile without requiring a sign-in
identity, even when the project declares named identities. The runner
can also select a recorded account if navigation reaches its account chooser.
authorize and verify still refuse a missing --identity when default
has no sites and another identity does. Each check gets a separate daemon,
including checks using the same identity at the same time. Its copy stays under
the machine's owner-only Yoke secret directory and is removed when the check
ends; copies whose owning process has exited are swept on the next browser
check. No copy leaves the machine or enters QA evidence.
For an interactive step sequence, qa browser setup --json returns a
check_id. Pass --check-id ID --keep-open on intermediate qa browser step
calls, then omit --keep-open on the final call to close and delete the copy.
A first step with --keep-open can also start the sequence and return its id.
Pass the same --project and --identity on every call.
Use qa browser stop --check-id ID to close it early, including after an error.
Inspect its PID and health with qa browser status --check-id ID --json.
An idle browser exits after ten minutes and its copy becomes sweepable.
--page NAME selects a page within that check, so two workers never share
pages merely by choosing the same identity or page name. An
exploratory mission can select personas with method_config.browser_identities,
for example ["operator"]; this list wins. With no list, dispatch supplies all
project-declared identities whose sites cover the target base URL origin,
including their site checks and accounts through the declarations. The walker
chooses the account the case needs. An uncovered target supplies no identities.
Any mission with selected identities
uses the managed browser even when it installs nothing; host-browser access
is reserved for missions naming none that have not installed Yoke.
The dispatch gives the walker an exact
yoke browser verify per identity to run before browsing. For an expired site,
the walker runs yoke browser authorize on that host and re-signs in itself,
or routes an approval to steering. Only a password or credential entry the
agent may not type returns WALK_STATUS: HUMAN_GATE, naming that one entry,
the identity, site, account and resume state.
Identities on test machines
Sites rotate session cookies, so a sealed copy of a signed-in profile is stale
by the time it is restored. A test machine therefore keeps its identities live
in ~/.yoke-browser-identities, outside ~/.yoke. A full reset keeps that
store the way it keeps harness logins, and golden capture never includes it,
while ~/.yoke itself is still absent after the reset. Once the walk installs
Yoke, each identity's profile path links into the store, so the product reads
and refreshes the live sessions in place and nothing is saved or restored.
Create the store once during host provisioning, owner-only (mkdir -m 700 ~/.yoke-browser-identities). A profile already signed in at its
capability-secrets path is moved into the store the first time it is resolved.
The Machine QA per-OS procedures at .yoke/docs/verification/test-machines/browser-identities.md
(installed by the Machine QA) cover desktop preparation and the first sign-in.