Agent Skillsrelaticle/relaticle › agent-browser-relaticle

agent-browser-relaticle

GitHub

针对 Relaticle 应用的 agent-browser 自动化测试指南,涵盖动态 URL 推导、登录流程、会话隔离及 Filament/Livewire 特定交互模式,支持自我修复。

.claude/skills/agent-browser-relaticle/SKILL.md relaticle/relaticle

Trigger Scenarios

需要对 Relaticle 应用进行 UI 自动化测试或 QA 执行前端业务逻辑的浏览器驱动测试 处理 Filament 面板的路由与登录问题

Install

npx skills add relaticle/relaticle --skill agent-browser-relaticle -g -y
More Options

Non-standard path

npx skills add https://github.com/relaticle/relaticle/tree/main/.claude/skills/agent-browser-relaticle -g -y

Use without installing

npx skills use relaticle/relaticle@agent-browser-relaticle

指定 Agent (Claude Code)

npx skills add relaticle/relaticle --skill agent-browser-relaticle -a claude-code -g -y

安装 repo 全部 skill

npx skills add relaticle/relaticle --all -g -y

预览 repo 内 skill

npx skills add relaticle/relaticle --list

SKILL.md

Frontmatter
{
    "name": "agent-browser-relaticle",
    "description": "Use whenever driving agent-browser against the local Relaticle app (relaticle.test and its panels) for testing, QA, business review, or UI automation. Covers Filament v5 + Livewire v4 quirks specific to this codebase: panel URL derivation (domain-routed vs path-routed, never assumed), login flows for the app and sysadmin panels, seeded credentials, Select\/date-picker interaction, the $wire.mountAction gold pattern, tenant switching, Reverb\/queue hazards, and session isolation. Every hard fact here is a DATED CACHED HINT. When one fails, re-derive from the running app and update this file (self-heal). Not for other sites or generic browser automation."
}

agent-browser × Relaticle cookbook (cached hints, verified dates, self-healing)

Prime rule: facts below are cached hints, not truth. The app's URLs, routes, selectors, and seeders change. When a documented pattern fails twice, stop retrying: re-derive it from the running app (procedures below), make it work, then update this file with the new pattern and today's verified: date.

1. URL derivation (NEVER hardcode; panels are conditionally domain-routed)

php artisan tinker --execute 'echo json_encode([
  "base"            => config("app.url"),
  "app_domain"      => config("app.app_panel_domain"),
  "app_path"        => config("app.app_panel_path", "app"),
  "sysadmin_domain" => config("app.sysadmin_domain"),
  "sysadmin_path"   => config("app.sysadmin_path", "sysadmin"),
]);'
  • app panel = https://{app_domain} if set, else {base}/{app_path}

  • sysadmin = https://{sysadmin_domain} if set, else {base}/{sysadmin_path}

  • Routing mode is per-checkout. Derive it, and never carry it over from another workspace. Both modes are live in the wild:

    • Conductor workspace bamako, APP_PANEL_DOMAIN/SYSADMIN_DOMAIN empty → path-routed: https://bamako.test/app, https://bamako.test/sysadmin (verified: 2026-08-12).
    • A checkout with the *_DOMAIN envs set → domain-routed, e.g. https://app.relaticle.test, https://sysadmin.relaticle.test (verified: 2026-06-12).

    Each Conductor workspace is served by Herd under its own https://<workspace>.test, so the host changes too. Run the tinker block above every run and use what it returns.

  • Login entry points are Filament-registered routes; ground truth: php artisan route:list --json filtered for login (names like filament.app.auth.login). If a URL 404s, check the route table before anything else.

  • Host unreachable? herd sites / herd links shows what Herd actually serves this checkout as (catches renamed dirs / Polyscope clones). .env vs config() mismatch → php artisan config:clear.

2. Session setup (every time)

export AB_SESSION="<purpose>-<run-id>"     # ALWAYS unique per agent; sessions are machine-global
agent-browser --session "$AB_SESSION" set viewport 1920 1080
agent-browser --session "$AB_SESSION" open "$APP_PANEL_URL"

Pass --session "$AB_SESSION" on EVERY call (or export AGENT_BROWSER_SESSION). Default 1280x720 clips Filament modals (verified: 2026-05).

3. Credentials (seeded; re-derive when login fails)

Surface Login Password Source
app panel manuk.minasyan1@gmail.com password database/seeders/LocalSeeder.php (verified: 2026-06-12)
sysadmin sysadmin@relaticle.com password SystemAdministratorSeeder (verified: 2026-06-12)
per-run test users br-rel-<run>-…@example.test password factory

Login failing? In order: php artisan db:seed --class=LocalSeeder (local-gated; also tops AI credits) → --class=SystemAdministratorSeeder → factory-create a namespaced user (User::factory()->withPersonalTeam()->create([...])). If the seeder emails changed, fix this table (self-heal).

Dev-login affordance: the app registers laravel-login-link (route loginLinkLogin, POST laravel-login-link-login; verified 2026-06-12 via route:list). Local login pages may render one-click "Login as …" links; prefer them over typing credentials when present.

4. Login flow (both panels, Filament stock login)

UPDATE (verified: 2026-09-03, app panel, path-routed astana-v1): the app login is now identifier-first (PR #285). /app/login renders only id="form.email" plus a "Continue" submit; there is no form.password on the first step, so the eval recipe below returns no-inputs. Two things that worked:

  • Herd's cert fails Chromium's name check (ERR_CERT_COMMON_NAME_INVALID). Export AGENT_BROWSER_IGNORE_HTTPS_ERRORS=1 (or pass --ignore-https-errors) on every call.
  • Local login pages render one-click laravel-login-link buttons labelled by email (owner@relaticle.test, trial@relaticle.test, ...). Click one via eval: [...document.querySelectorAll("button[type=submit]")].find(b=>b.innerText.trim()==="owner@relaticle.test").click() and you land on /app/<team-slug> (acme-sales for owner) with no password step.

CORRECTION (verified: 2026-06-12, review PR 336): the input[name="email"] selector is WRONG. It matches a hidden input belonging to the laravel-login-link dev package (the page has hidden _token/email/key/guard/user_model inputs from that form). agent-browser fill against that hidden field hung the daemon (os error 35, "daemon may be busy or unresponsive") and never submitted. The REAL Filament inputs have NO name attribute. They are id="form.email" / id="form.password" with wire:model="data.email" / data.password, inside the <form wire:submit="authenticate">.

The recipe that works when fill/type hang (eval-driven, daemon-safe):

export AGENT_BROWSER_SESSION="<unique>"
agent-browser open "$PANEL_URL/login"
agent-browser eval '(() => {
  const e=document.getElementById("form.email"), p=document.getElementById("form.password");
  e.value="'"$LOGIN"'"; e.dispatchEvent(new Event("input",{bubbles:true}));
  p.value="password";  p.dispatchEvent(new Event("input",{bubbles:true}));
  const f=[...document.querySelectorAll("form")].find(x=>x.getAttribute("wire:submit")==="authenticate");
  f.requestSubmit(); return "submitted";
})()'
sleep 4
agent-browser eval 'location.pathname'   # confirm you left /login (lands on /<team-slug>)
  • Daemon hangs on fill/type in this environment (verified: 2026-06-12). When a command returns os error 35 / no output, pkill -9 -f agent-browser; sleep 3 and re-open. open/eval/snapshot/screenshot are reliable; click is flaky, so prefer eval with el.click() for <a wire:navigate> links.
  • Many stale --session entries overload the daemon; keep ONE session per run and chain commands with && in a single shell call (the daemon persists the browser).
Older recipe (fill+click), left here for reference; did NOT work on 2026-06-12
agent-browser --session "$AB" open "$PANEL_URL/login"
agent-browser --session "$AB" fill 'input[name="email"]' "$LOGIN"
agent-browser --session "$AB" fill 'input[name="password"]' "password"
sleep 1
agent-browser --session "$AB" click "Sign in"
agent-browser --session "$AB" wait --load networkidle
agent-browser --session "$AB" eval 'location.pathname'   # confirm you left /login
  • click / fill take the element's VISIBLE TEXT or a CSS selector, NOT find role button "<name>". That subcommand syntax errors on this binary (verified: 2026-06-12). Use agent-browser click "Sign in".
  • After app-panel login you land on the default team path …/<team-slug>/… (e.g. /tapix), so re-derive the slug from location.pathname before navigating further (verified: 2026-06-12).
  • After sysadmin login you land on / (Dashboard) on sysadmin.relaticle.test (verified: 2026-06-12).
  • A "Developer Login" button is present on the login page but clicking it alone did not establish a session in testing, so prefer the fill+click recipe above (verified: 2026-06-12).

4b. Screenshot paths: ALWAYS absolute

agent-browser screenshot parses a RELATIVE path containing / as a CSS selector and fails (Unexpected token "/" while parsing css selector). Always pass an absolute path: agent-browser --session "$AB" screenshot "$(pwd)/.context/reviews/<dir>/case-X/shot.png" (verified: 2026-06-12).

5. The gold patterns (Filament v5 + Livewire v4)

Prefer semantics over CSS selectors. a11y-role finds and Livewire state survive Blade/Tailwind refactors.

$wire is NOT in scope inside agent-browser eval (it's an Alpine magic; eval runs in plain page context (verified 2026-06-12, after it cost a run 4 round-trips and one self-inflicted 500 where the server was asked to call a method literally named $wire). Resolve the component first, then use .set(...) / .call(...):

// by name (page components):
const meta = window.Livewire.all().find(c => /TasksBoard/.test(c.name)); // metadata ONLY: {id, name}
const comp = window.Livewire.find(meta.id);                              // the real component
await comp.call("moveCard", "<recordId>", "<columnId>");
// or from a DOM element (modals, nested components):
const comp2 = window.Livewire.find(el.closest("[wire\\:id]").getAttribute("wire:id"));
await comp2.set("mountedActions.0.data.title", "value", true);
await comp2.call("callMountedAction");
  • Livewire.all() entries have NO .call/.set. They are metadata, so always pass the id through Livewire.find() (verified: 2026-06-12).
  • Select dropdowns (plain click is unreliable): agent-browser find role combobox "<label>" click then agent-browser find role option "<option>" click, or comp.set("data.company_id", 42, true).
  • Date pickers (plain type does nothing): comp.set("data.closes_at", "2026-06-15", true).
  • Action modals (Delete, custom row actions), the single most useful pattern:
    await comp.call("mountAction", "delete", { recordKey: 42 });
    await comp.set("mountedActions.0.data.reason", "why", true);
    await comp.call("callMountedAction");
    
    Every call must be await-ed. (verified: 2026-06-12 via the create-task modal)
  • Read Livewire state: agent-browser eval '... JSON.stringify(comp.get("data"))'
  • Snapshots: agent-browser snapshot -i -c -d 8 (focused), never bare snapshot. Refs (@eXX) shift between snapshots, so keep snapshot→interaction adjacent or use find role/text … click.
  • Modals fade ~300ms, so use agent-browser wait '.fi-modal-window:not([data-state="open"])' 2000 before asserting removal.
  • Tenant switching is browser-only (in-app switcher; tinker tenant-switch breaks the session → persistent 403s). After a switch the URL slug changes, so re-derive.

6. Environment hazards (dated)

  • Factory/fresh teams redirect every app-panel page to /billing (verified: 2026-08-25): EnsureHostedWorkspaceAccess allows only subscribed teams or trial_ends_at in the future, and factory teams have neither, so login lands on /app/<slug>/billing and stays there. Fix before browsing: $team->forceFill(['trial_ends_at' => now()->addDays(14)])->save(); (LocalSeeder's user is already provisioned; this bites ChatQaSeeder and factory users).
  • Shared local Redis across Herd apps: another app's Horizon can consume this app's queue jobs (verified 2026-06-11, when Journey ate Relaticle chat jobs). Use a dedicated REDIS_DB in .env; before queue-dependent testing, dispatch a sentinel job and confirm THIS checkout's worker consumed it.
  • Reverb/websockets: agent-browser's Chromium may use a wrong websocket host or a stale built bundle. It looks like a dead page but is an env defect. pnpm run build, check agent-browser console for websocket errors (verified: 2026-06-10).
  • 419 CSRF after idleagent-browser reload and retry once (verified: 2026-05).
  • A failed Livewire request leaves a full-screen error overlay in the DOM (Laravel error page in a modal) that silently photobombs every later screenshot. The page underneath still works, so nothing looks wrong until you read the PNG back. After ANY errored comp.call, agent-browser open the page fresh (or remove the overlay) before shooting (verified 2026-06-12, when a stale overlay replaced the board in an evidence shot).
  • Stale session after branch switches → first action of a batch is a fresh login.
  • AI credits drain during chat testing → re-seed LocalSeeder to top up before chat-heavy flows.
  • Screenshot pipeline can serve STALE FRAMES from a dead target (verified: 2026-08-18): after long sessions / viewport changes, screenshot kept returning a frame that no longer matched the DOM (evals said dark theme + correct state; PNG showed an old light half-render). Detection: eval 'document.body.style.outline="40px solid red"' → screenshot → if no red border, the pipeline is stale. Fix: pkill -9 -f agent-browser; sleep 3, new session, re-login. Don't debug the "bug" in the PNG before running the red-outline probe. UPDATE (verified: 2026-08-23): the pkill+new-session fix did NOT clear it, a fresh session's very first screenshots again failed the red-outline probe (dark-mode text read black in the PNG while getComputedStyle + elementFromPoint at the same coordinates said white). When the probe fails twice, stop shooting: assert via DOM reads (computed styles, elementFromPoint, rects) and treat those as the truth for visual verification.

7. DB-assert (corroboration only; the UI is the proof)

php artisan tinker --execute '$c = \App\Models\Company::where("name", "br-rel-test")->first(); echo $c ? "found:".$c->id : "missing";'

Tenant-scoped query? Set context first: \Relaticle\CustomFields\Services\TenantContextService::setTenantId($teamId); Never use tinker or DB writes to fix or fake a result. An on-screen error is a finding.

8. Screenshots

For any deliverable screenshot, invoke Skill('screenshot-with-callout') per shot (annotate → verify-crop → shoot → read-back). Throwaway debug shots exempt.

9. Eval and rendering hints (verified: 2026-09-07)

  • agent-browser eval runs every call in the same page scope. A top-level const x declared in one eval throws Identifier 'x' has already been declared in the next. Wrap evals in an IIFE: agent-browser eval '(()=>{ const x=...; return JSON.stringify(x) })()'.
  • To screenshot a feature-flag branch without flipping the shared .env, render it to a file and open that: php artisan tinker --execute '\Laravel\Pennant\Feature::define(\App\Features\Billing::class, false); file_put_contents(".context/off.html", view("pricing")->render());' then agent-browser open "file://$(pwd)/.context/off.html". Vite assets resolve to the absolute APP_URL, so the page styles correctly from file://.

10. Turnstile on the signup step (verified: 2026-09-10)

  • The widget lives in a closed shadow root, so document.querySelector("iframe[src*=challenges]") is always null. Read the enclosing .fi-grid-col instead: fi-hidden = silent pass, 70px tall = checkbox shown. The schema's grid child is .fi-grid-col, not .fi-fo-field; an empty in-flow column still costs one 24px grid gap, so measure password-field-bottom to button-top (24 = clean). Cloudflare's dummy sitekeys drive each state: 1x…AA passes silently, 3x…FF forces the checkbox, 2x…AB always fails; secret 1x…AA accepts the dummy token.
  • To click the checkbox use coordinates: agent-browser mouse move X Y && mouse down && mouse up at rect.x+20, rect.y+32. Under zsh mouse move $XY fails with "Missing arguments": an unquoted variable is not word-split, so read -r X Y <<< "$XY" first.
  • agent-browser set media dark exists; toggling document.documentElement.classList also works.

Version History

  • 2e5f32c Current 2026-09-23 05:34

    无变更

  • f11c94b 2026-08-29 02:19

    重构聊天功能,移除硬编码提示和重复的状态展示;修复面板路由判断逻辑及 UI 层级渲染问题。

  • 1f433f8 2026-08-20 14:42

Same Skill Collection

.claude/skills/echo-development/SKILL.md
.claude/skills/filament-development/SKILL.md
.claude/skills/laravel-query-builder/SKILL.md
.claude/skills/medialibrary-development/SKILL.md
.claude/skills/socialite-development/SKILL.md
.claude/skills/spatie-javascript/SKILL.md
.claude/skills/spatie-laravel-php/SKILL.md
.claude/skills/spatie-security/SKILL.md
.claude/skills/spatie-version-control/SKILL.md
.claude/skills/testing-best-practices/SKILL.md
.github/skills/custom-fields-development/SKILL.md
.github/skills/flowforge-development/SKILL.md
.claude/skills/ai-sdk-development/SKILL.md
.claude/skills/business-review/SKILL.md
.claude/skills/cashier-stripe-development/SKILL.md
.claude/skills/configuring-horizon/SKILL.md
.claude/skills/fortify-development/SKILL.md
.claude/skills/infer-conventions/SKILL.md
.claude/skills/laravel-best-practices/SKILL.md
.claude/skills/livewire-development/SKILL.md
.claude/skills/mcp-development/SKILL.md
.claude/skills/passport-development/SKILL.md
.claude/skills/pennant-development/SKILL.md
.claude/skills/pest-testing/SKILL.md
.claude/skills/screenshot-with-callout/SKILL.md
.claude/skills/sluggable-development/SKILL.md
.claude/skills/tailwindcss-development/SKILL.md
.github/skills/livewire-development/SKILL.md
.github/skills/manual-testing/SKILL.md
.github/skills/tailwindcss-development/SKILL.md

Metadata

Files
0
Version
2e5f32c
Hash
55069b1c
Indexed
2026-08-20 14:42

trang chủ - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-23 12:14
浙ICP备14020137号-1