rhachet-roles-bhrowser 0.0.0
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/LICENSE +21 -0
- package/dist/contract/sdk/index.d.ts +3 -0
- package/dist/contract/sdk/index.js +10 -0
- package/dist/contract/sdk/index.js.map +1 -0
- package/dist/domain.roles/getRoleRegistry.d.ts +6 -0
- package/dist/domain.roles/getRoleRegistry.js +17 -0
- package/dist/domain.roles/getRoleRegistry.js.map +1 -0
- package/dist/domain.roles/inspector/boot.yml +4 -0
- package/dist/domain.roles/inspector/getInspectorRole.d.ts +6 -0
- package/dist/domain.roles/inspector/getInspectorRole.js +34 -0
- package/dist/domain.roles/inspector/getInspectorRole.js.map +1 -0
- package/dist/domain.roles/inspector/readme.md +7 -0
- package/dist/domain.roles/playwright/boot.yml +37 -0
- package/dist/domain.roles/playwright/briefs/auth/howto.cross-session-auth.md +134 -0
- package/dist/domain.roles/playwright/briefs/debug/howto.capture-state-on-error.md +253 -0
- package/dist/domain.roles/playwright/briefs/debug/howto.debug-movie-frames.md +182 -0
- package/dist/domain.roles/playwright/briefs/diagnosis/howto.browser-diagnosis.md +152 -0
- package/dist/domain.roles/playwright/briefs/diagnosis/rule.require.grep-html-before-selector-guess.md +82 -0
- package/dist/domain.roles/playwright/briefs/diagnosis/rule.require.snapshot-before-assume.md +73 -0
- package/dist/domain.roles/playwright/briefs/diagnosis/rule.require.snapshot-before-debug.md +81 -0
- package/dist/domain.roles/playwright/briefs/diagnosis/rule.require.snapshot-latest-tab.md +84 -0
- package/dist/domain.roles/playwright/briefs/reliability/ref.cdp-reconnection-patterns.md +168 -0
- package/dist/domain.roles/playwright/briefs/reliability/rule.forbid.coordinate-clicks.md +53 -0
- package/dist/domain.roles/playwright/briefs/reliability/rule.require.bounded-timeouts-and-bisection.md +184 -0
- package/dist/domain.roles/playwright/briefs/rule.im_a.bhrowser_lizard.md +68 -0
- package/dist/domain.roles/playwright/briefs/skills/howto.browser-action-playbooks.md +265 -0
- package/dist/domain.roles/playwright/briefs/skills/howto.browser-byhand-work.md +151 -0
- package/dist/domain.roles/playwright/briefs/skills/rule.require.playbooks-over-adhoc.md +90 -0
- package/dist/domain.roles/playwright/briefs/spa/rule.require.spa-navigation-fail-fast.md +109 -0
- package/dist/domain.roles/playwright/briefs/spa/rule.require.wait-for-content-not-shell.md +128 -0
- package/dist/domain.roles/playwright/briefs/spa/rule.require.wait-for-react-render.md +127 -0
- package/dist/domain.roles/playwright/briefs/spa/rule.require.wait-for-target-element.md +123 -0
- package/dist/domain.roles/playwright/briefs/stealth/ref.antibot-escalation.md +120 -0
- package/dist/domain.roles/playwright/briefs/test/howto.test-via-browser.md +285 -0
- package/dist/domain.roles/playwright/briefs/test/rule.forbid.afterall-state-mutation.md +106 -0
- package/dist/domain.roles/playwright/briefs/test/rule.forbid.test-goto.md +77 -0
- package/dist/domain.roles/playwright/briefs/test/rule.require.fast-tests.md +130 -0
- package/dist/domain.roles/playwright/briefs/verification/rule.forbid.unverified-actions.md +95 -0
- package/dist/domain.roles/playwright/briefs/verification/rule.require.action-verification.md +138 -0
- package/dist/domain.roles/playwright/getPlaywrightRole.d.ts +6 -0
- package/dist/domain.roles/playwright/getPlaywrightRole.js +34 -0
- package/dist/domain.roles/playwright/getPlaywrightRole.js.map +1 -0
- package/dist/domain.roles/playwright/readme.md +7 -0
- package/dist/domain.roles/playwright/skills/.test/infra/browser.ts +272 -0
- package/dist/domain.roles/playwright/skills/browser.action.d.ts +23 -0
- package/dist/domain.roles/playwright/skills/browser.action.js +124 -0
- package/dist/domain.roles/playwright/skills/browser.action.js.map +1 -0
- package/dist/domain.roles/playwright/skills/browser.action.sh +120 -0
- package/dist/domain.roles/playwright/skills/browser.action.ts +148 -0
- package/dist/domain.roles/playwright/skills/browser.describe.d.ts +15 -0
- package/dist/domain.roles/playwright/skills/browser.describe.js +191 -0
- package/dist/domain.roles/playwright/skills/browser.describe.js.map +1 -0
- package/dist/domain.roles/playwright/skills/browser.describe.sh +45 -0
- package/dist/domain.roles/playwright/skills/browser.describe.ts +255 -0
- package/dist/domain.roles/playwright/skills/browser.lib.sh +725 -0
- package/dist/domain.roles/playwright/skills/browser.session.d.ts +1 -0
- package/dist/domain.roles/playwright/skills/browser.session.js +253 -0
- package/dist/domain.roles/playwright/skills/browser.session.js.map +1 -0
- package/dist/domain.roles/playwright/skills/browser.session.sh +194 -0
- package/dist/domain.roles/playwright/skills/browser.session.ts +310 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.console.d.ts +33 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.console.js +163 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.console.js.map +1 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.console.sh +57 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.console.ts +212 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.html.d.ts +17 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.html.js +133 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.html.js.map +1 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.html.sh +53 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.html.ts +152 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.meta.d.ts +28 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.meta.js +109 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.meta.js.map +1 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.meta.sh +53 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.meta.ts +136 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.network.d.ts +28 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.network.js +160 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.network.js.map +1 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.network.sh +57 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.network.ts +188 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.screen.d.ts +17 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.screen.js +138 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.screen.js.map +1 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.screen.sh +65 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.screen.ts +187 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.sh +323 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.storage.d.ts +21 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.storage.js +154 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.storage.js.map +1 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.storage.sh +53 -0
- package/dist/domain.roles/playwright/skills/browser.snapshot.storage.ts +172 -0
- package/dist/domain.roles/playwright/skills/browser.start.sh +266 -0
- package/dist/domain.roles/playwright/skills/browser.stop.sh +89 -0
- package/dist/domain.roles/playwright/skills/lib/shared.d.ts +219 -0
- package/dist/domain.roles/playwright/skills/lib/shared.js +416 -0
- package/dist/domain.roles/playwright/skills/lib/shared.js.map +1 -0
- package/dist/domain.roles/playwright/skills/lib/shared.ts +480 -0
- package/dist/domain.roles/readme.md +8 -0
- package/dist/domain.roles/scraper/boot.yml +8 -0
- package/dist/domain.roles/scraper/briefs/cache/howto.cache-browser-scrapes.md +257 -0
- package/dist/domain.roles/scraper/briefs/cache/howto.setup-cache-infrastructure.md +62 -0
- package/dist/domain.roles/scraper/briefs/cache/ref.remote-state-query-cache.md +43 -0
- package/dist/domain.roles/scraper/briefs/cache/rule.require.remote-state-cache.md +119 -0
- package/dist/domain.roles/scraper/getScraperRole.d.ts +6 -0
- package/dist/domain.roles/scraper/getScraperRole.js +34 -0
- package/dist/domain.roles/scraper/getScraperRole.js.map +1 -0
- package/dist/domain.roles/scraper/readme.md +7 -0
- package/dist/domain.roles/scraper/skills/.test/infra/cache.ts +184 -0
- package/dist/domain.roles/scraper/skills/cache.expire.sh +159 -0
- package/dist/domain.roles/scraper/skills/cache.extend.sh +416 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/license.md +21 -0
- package/package.json +116 -0
- package/readme.[seed].md +2 -0
- package/readme.md +65 -0
- package/rhachet.repo.yml +17 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Uladzimir Kasacheuski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ROLE_PLAYWRIGHT = exports.ROLE_INSPECTOR = exports.getRoleRegistry = void 0;
|
|
4
|
+
var getRoleRegistry_1 = require("../../domain.roles/getRoleRegistry");
|
|
5
|
+
Object.defineProperty(exports, "getRoleRegistry", { enumerable: true, get: function () { return getRoleRegistry_1.getRoleRegistry; } });
|
|
6
|
+
var getInspectorRole_1 = require("../../domain.roles/inspector/getInspectorRole");
|
|
7
|
+
Object.defineProperty(exports, "ROLE_INSPECTOR", { enumerable: true, get: function () { return getInspectorRole_1.ROLE_INSPECTOR; } });
|
|
8
|
+
var getPlaywrightRole_1 = require("../../domain.roles/playwright/getPlaywrightRole");
|
|
9
|
+
Object.defineProperty(exports, "ROLE_PLAYWRIGHT", { enumerable: true, get: function () { return getPlaywrightRole_1.ROLE_PLAYWRIGHT; } });
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/contract/sdk/index.ts"],"names":[],"mappings":";;;AAAA,sEAAqE;AAA5D,kHAAA,eAAe,OAAA;AACxB,kFAA+E;AAAtE,kHAAA,cAAc,OAAA;AACvB,qFAAkF;AAAzE,oHAAA,eAAe,OAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRoleRegistry = void 0;
|
|
4
|
+
const rhachet_1 = require("rhachet");
|
|
5
|
+
const getInspectorRole_1 = require("./inspector/getInspectorRole");
|
|
6
|
+
const getPlaywrightRole_1 = require("./playwright/getPlaywrightRole");
|
|
7
|
+
/**
|
|
8
|
+
* .what = returns the bhrowser registry of predefined roles
|
|
9
|
+
* .why = enables rhachet to discover and enroll bhrowser roles
|
|
10
|
+
*/
|
|
11
|
+
const getRoleRegistry = () => rhachet_1.RoleRegistry.build({
|
|
12
|
+
slug: 'bhrowser',
|
|
13
|
+
readme: { uri: `${__dirname}/readme.md` },
|
|
14
|
+
roles: [getPlaywrightRole_1.ROLE_PLAYWRIGHT, getInspectorRole_1.ROLE_INSPECTOR],
|
|
15
|
+
});
|
|
16
|
+
exports.getRoleRegistry = getRoleRegistry;
|
|
17
|
+
//# sourceMappingURL=getRoleRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getRoleRegistry.js","sourceRoot":"","sources":["../../src/domain.roles/getRoleRegistry.ts"],"names":[],"mappings":";;;AAAA,qCAAuC;AAEvC,mEAA8D;AAC9D,sEAAiE;AAEjE;;;GAGG;AACI,MAAM,eAAe,GAAG,GAAiB,EAAE,CAChD,sBAAY,CAAC,KAAK,CAAC;IACjB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,SAAS,YAAY,EAAE;IACzC,KAAK,EAAE,CAAC,mCAAe,EAAE,iCAAc,CAAC;CACzC,CAAC,CAAC;AALQ,QAAA,eAAe,mBAKvB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ROLE_INSPECTOR = void 0;
|
|
4
|
+
const rhachet_1 = require("rhachet");
|
|
5
|
+
/**
|
|
6
|
+
* .what = returns the inspector role definition
|
|
7
|
+
* .why = enables rhachet to enroll brains with inspector capabilities
|
|
8
|
+
*/
|
|
9
|
+
exports.ROLE_INSPECTOR = rhachet_1.Role.build({
|
|
10
|
+
slug: 'inspector',
|
|
11
|
+
name: 'Inspector',
|
|
12
|
+
purpose: 'inspect browser renders for performance, precision, and aesthetics',
|
|
13
|
+
traits: [],
|
|
14
|
+
readme: { uri: `${__dirname}/readme.md` },
|
|
15
|
+
boot: { uri: `${__dirname}/boot.yml` },
|
|
16
|
+
briefs: {
|
|
17
|
+
dirs: [{ uri: `${__dirname}/briefs` }],
|
|
18
|
+
},
|
|
19
|
+
skills: {
|
|
20
|
+
dirs: [{ uri: `${__dirname}/skills` }],
|
|
21
|
+
refs: [],
|
|
22
|
+
},
|
|
23
|
+
hooks: {
|
|
24
|
+
onBrain: {
|
|
25
|
+
onBoot: [
|
|
26
|
+
{
|
|
27
|
+
command: './node_modules/.bin/rhachet roles boot --repo bhrowser --role inspector',
|
|
28
|
+
timeout: 'PT60S',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=getInspectorRole.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getInspectorRole.js","sourceRoot":"","sources":["../../../src/domain.roles/inspector/getInspectorRole.ts"],"names":[],"mappings":";;;AAAA,qCAA+B;AAE/B;;;GAGG;AACU,QAAA,cAAc,GAAS,cAAI,CAAC,KAAK,CAAC;IAC7C,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,oEAAoE;IAC7E,MAAM,EAAE,EAAE;IACV,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,SAAS,YAAY,EAAE;IACzC,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,SAAS,WAAW,EAAE;IACtC,MAAM,EAAE;QACN,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,SAAS,SAAS,EAAE,CAAC;KACvC;IACD,MAAM,EAAE;QACN,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,SAAS,SAAS,EAAE,CAAC;QACtC,IAAI,EAAE,EAAE;KACT;IACD,KAAK,EAAE;QACL,OAAO,EAAE;YACP,MAAM,EAAE;gBACN;oBACE,OAAO,EACL,yEAAyE;oBAC3E,OAAO,EAAE,OAAO;iBACjB;aACF;SACF;KACF;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## 📐 inspector
|
|
2
|
+
|
|
3
|
+
- **scale**: visual-level, design assessment
|
|
4
|
+
- **focus**: inspect performance, precision, and aesthetics — screenshots, diffs, regressions
|
|
5
|
+
- **maximizes**: visual accuracy and design coherence
|
|
6
|
+
|
|
7
|
+
used to assess browser renders, spot visual regressions, and ensure design consistency.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
always:
|
|
2
|
+
briefs:
|
|
3
|
+
say:
|
|
4
|
+
# core rules - loaded in full
|
|
5
|
+
- briefs/diagnosis/rule.require.snapshot-before-assume.md
|
|
6
|
+
- briefs/diagnosis/rule.require.snapshot-before-debug.md
|
|
7
|
+
- briefs/verification/rule.require.action-verification.md
|
|
8
|
+
- briefs/spa/rule.require.wait-for-target-element.md
|
|
9
|
+
ref:
|
|
10
|
+
# diagnosis
|
|
11
|
+
- briefs/diagnosis/howto.browser-diagnosis.md
|
|
12
|
+
- briefs/diagnosis/rule.require.snapshot-latest-tab.md
|
|
13
|
+
- briefs/diagnosis/rule.require.grep-html-before-selector-guess.md
|
|
14
|
+
# verification
|
|
15
|
+
- briefs/verification/rule.forbid.unverified-actions.md
|
|
16
|
+
# spa
|
|
17
|
+
- briefs/spa/rule.require.spa-navigation-fail-fast.md
|
|
18
|
+
- briefs/spa/rule.require.wait-for-content-not-shell.md
|
|
19
|
+
- briefs/spa/rule.require.wait-for-react-render.md
|
|
20
|
+
# auth
|
|
21
|
+
- briefs/auth/howto.cross-session-auth.md
|
|
22
|
+
# stealth
|
|
23
|
+
- briefs/stealth/ref.antibot-escalation.md
|
|
24
|
+
# reliability
|
|
25
|
+
- briefs/reliability/ref.cdp-reconnection-patterns.md
|
|
26
|
+
- briefs/reliability/rule.require.bounded-timeouts-and-bisection.md
|
|
27
|
+
# debug
|
|
28
|
+
- briefs/debug/howto.debug-movie-frames.md
|
|
29
|
+
- briefs/debug/howto.capture-state-on-error.md
|
|
30
|
+
# skills
|
|
31
|
+
- briefs/skills/howto.browser-action-playbooks.md
|
|
32
|
+
- briefs/skills/howto.browser-byhand-work.md
|
|
33
|
+
# test
|
|
34
|
+
- briefs/test/howto.test-via-browser.md
|
|
35
|
+
- briefs/test/rule.forbid.test-goto.md
|
|
36
|
+
- briefs/test/rule.forbid.afterall-state-mutation.md
|
|
37
|
+
- briefs/test/rule.require.fast-tests.md
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# howto.cross-session-auth
|
|
2
|
+
|
|
3
|
+
## .what
|
|
4
|
+
|
|
5
|
+
persist authentication across browser sessions via playwright's `storageState`.
|
|
6
|
+
|
|
7
|
+
## .why
|
|
8
|
+
|
|
9
|
+
- human solves captcha once, robot reuses auth indefinitely
|
|
10
|
+
- login flows run once per credential lifetime
|
|
11
|
+
- headful ↔ headless handoff without re-authentication
|
|
12
|
+
|
|
13
|
+
## .pattern
|
|
14
|
+
|
|
15
|
+
### save session after authentication
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// after login completes
|
|
19
|
+
const storageState = await context.storageState();
|
|
20
|
+
await fs.writeFile('./auth-state.json', JSON.stringify(storageState, null, 2));
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### restore session in new browser
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// new browser instance, same auth
|
|
27
|
+
const context = await browser.newContext({
|
|
28
|
+
storageState: './auth-state.json',
|
|
29
|
+
});
|
|
30
|
+
// all cookies, localStorage, sessionStorage restored
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## .storage state contents
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"cookies": [
|
|
38
|
+
{
|
|
39
|
+
"name": "session_id",
|
|
40
|
+
"value": "abc123...",
|
|
41
|
+
"domain": ".example.com",
|
|
42
|
+
"path": "/",
|
|
43
|
+
"expires": 1735689600,
|
|
44
|
+
"httpOnly": true,
|
|
45
|
+
"secure": true,
|
|
46
|
+
"sameSite": "Lax"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"origins": [
|
|
50
|
+
{
|
|
51
|
+
"origin": "https://example.com",
|
|
52
|
+
"localStorage": [
|
|
53
|
+
{ "name": "authToken", "value": "xyz789..." }
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## .workflow: captcha handoff
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# 1. human session (headful)
|
|
64
|
+
browser.start --session human --mode HEADFUL
|
|
65
|
+
# human navigates to login, solves captcha, authenticates
|
|
66
|
+
|
|
67
|
+
# 2. export auth state
|
|
68
|
+
browser.session get --session human --into ./auth-state.json
|
|
69
|
+
|
|
70
|
+
# 3. stop human session
|
|
71
|
+
browser.stop --session human
|
|
72
|
+
|
|
73
|
+
# 4. robot session (headless)
|
|
74
|
+
browser.start --session robot --mode HEADLESS
|
|
75
|
+
browser.session set --session robot --from ./auth-state.json
|
|
76
|
+
|
|
77
|
+
# 5. robot works with human's auth
|
|
78
|
+
browser.action --session robot --play ./scrape-data.play.ts
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## .credential lifetime
|
|
82
|
+
|
|
83
|
+
| credential type | typical lifetime | refresh strategy |
|
|
84
|
+
|-----------------|------------------|------------------|
|
|
85
|
+
| session cookie | hours to days | re-login on expiry |
|
|
86
|
+
| jwt token | 15 min to 1 hour | refresh token flow |
|
|
87
|
+
| oauth token | varies | refresh endpoint |
|
|
88
|
+
| remember-me | weeks to months | periodic re-auth |
|
|
89
|
+
|
|
90
|
+
## .best practices
|
|
91
|
+
|
|
92
|
+
### secure storage
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# encrypt at rest
|
|
96
|
+
gpg -c ./auth-state.json
|
|
97
|
+
|
|
98
|
+
# restrict permissions
|
|
99
|
+
chmod 600 ./auth-state.json
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### session isolation
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# different sessions for different accounts
|
|
106
|
+
browser.session set --session account-a --from ./auth-a.json
|
|
107
|
+
browser.session set --session account-b --from ./auth-b.json
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### expiry detection
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { BadRequestError } from 'helpful-errors';
|
|
114
|
+
|
|
115
|
+
// in playbook, detect session expiry
|
|
116
|
+
export const action = async (input: { page: Page }) => {
|
|
117
|
+
await input.page.goto('https://example.com/dashboard');
|
|
118
|
+
|
|
119
|
+
// check if redirected to login
|
|
120
|
+
if (input.page.url().includes('/login')) {
|
|
121
|
+
throw new BadRequestError('session expired', {
|
|
122
|
+
url: input.page.url(),
|
|
123
|
+
hint: 're-authenticate via browser.session set --from @storage',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// continue with authenticated work
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## .see also
|
|
132
|
+
|
|
133
|
+
- `stealth/ref.antibot-escalation.md` — when auth alone fails
|
|
134
|
+
- `browser.session.sh` — session management skill
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# howto.capture-state-on-error
|
|
2
|
+
|
|
3
|
+
## .what
|
|
4
|
+
|
|
5
|
+
automatically capture browser state when errors occur.
|
|
6
|
+
|
|
7
|
+
## .why
|
|
8
|
+
|
|
9
|
+
errors are transient:
|
|
10
|
+
- page state changes after error
|
|
11
|
+
- console messages scroll away
|
|
12
|
+
- network requests complete
|
|
13
|
+
- element disappears
|
|
14
|
+
|
|
15
|
+
capture immediately preserves evidence.
|
|
16
|
+
|
|
17
|
+
## .pattern
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { HelpfulError } from 'helpful-errors';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* .what = get current timestamp in milliseconds
|
|
24
|
+
* .why = isolates Date.now() call from orchestrator
|
|
25
|
+
*/
|
|
26
|
+
const asTimestamp = (): number => Date.now();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* .what = get current time as ISO string
|
|
30
|
+
* .why = isolates Date conversion from orchestrator
|
|
31
|
+
*/
|
|
32
|
+
const asIsoTimestamp = (): string => new Date().toJSON();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* .what = get capture directory path with fallback to timestamp
|
|
36
|
+
* .why = isolates nullable coalesce + path construction
|
|
37
|
+
*/
|
|
38
|
+
const asCaptureDir = (input: { captureId: string | null }): string =>
|
|
39
|
+
`.cache/errors/${input.captureId ?? String(asTimestamp())}`;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* .what = wrap browser action with automatic error state capture
|
|
43
|
+
* .why = preserves evidence when errors occur for post-mortem analysis
|
|
44
|
+
*/
|
|
45
|
+
export const action = async (
|
|
46
|
+
input: Record<string, never>,
|
|
47
|
+
context: { page: Page; browser: Browser },
|
|
48
|
+
) => {
|
|
49
|
+
return HelpfulError.wrap(
|
|
50
|
+
async () => {
|
|
51
|
+
await riskyOperation(context.page);
|
|
52
|
+
return { success: true };
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
message: 'browser action failed',
|
|
56
|
+
metadata: { url: context.page.url() },
|
|
57
|
+
onError: async (error) => {
|
|
58
|
+
// capture state before it changes
|
|
59
|
+
await setErrorSnapshot({ error, captureId: null }, { page: context.page });
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
)();
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* .what = persist browser state snapshot when error occurs
|
|
67
|
+
* .why = preserves screenshot, html, and error details for diagnosis
|
|
68
|
+
*/
|
|
69
|
+
const setErrorSnapshot = async (
|
|
70
|
+
// captureId nullable: caller may not have identifier; defaults to timestamp
|
|
71
|
+
input: { error: Error; captureId: string | null },
|
|
72
|
+
context: { page: Page },
|
|
73
|
+
) => {
|
|
74
|
+
// derive artifact path via named transformer
|
|
75
|
+
const dirError = asCaptureDir({ captureId: input.captureId });
|
|
76
|
+
await fs.mkdir(dirError, { recursive: true });
|
|
77
|
+
|
|
78
|
+
// screenshot
|
|
79
|
+
await context.page.screenshot({
|
|
80
|
+
path: `${dirError}/screenshot.png`,
|
|
81
|
+
fullPage: true,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// html
|
|
85
|
+
await fs.writeFile(`${dirError}/page.html`, await context.page.content());
|
|
86
|
+
|
|
87
|
+
// console logs (captured earlier via event handler - not available in error handler)
|
|
88
|
+
// .note = console messages should be captured proactively, not in error handler
|
|
89
|
+
|
|
90
|
+
// error details
|
|
91
|
+
await fs.writeFile(`${dirError}/error.json`, JSON.stringify({
|
|
92
|
+
message: input.error.message,
|
|
93
|
+
stack: input.error.stack,
|
|
94
|
+
url: context.page.url(),
|
|
95
|
+
title: await context.page.title(),
|
|
96
|
+
time: asIsoTimestamp(),
|
|
97
|
+
}, null, 2));
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## .wrapper pattern
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { HelpfulError } from 'helpful-errors';
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* .what = higher-order function that wraps playbooks with error snapshot
|
|
108
|
+
* .why = enables consistent error state capture across all playbooks
|
|
109
|
+
*/
|
|
110
|
+
const withErrorSnapshot = <I, R>(
|
|
111
|
+
action: (input: I, context: { page: Page }) => Promise<R>
|
|
112
|
+
) => {
|
|
113
|
+
return async (input: I, context: { page: Page }): Promise<R> => {
|
|
114
|
+
return HelpfulError.wrap(
|
|
115
|
+
async () => action(input, context),
|
|
116
|
+
{
|
|
117
|
+
message: 'browser action failed',
|
|
118
|
+
metadata: { url: context.page.url() },
|
|
119
|
+
onError: async (error) => {
|
|
120
|
+
// capture state before re-throw
|
|
121
|
+
await setErrorSnapshot({ error, captureId: null }, { page: context.page });
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
)();
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// usage: define _action then wrap
|
|
129
|
+
/**
|
|
130
|
+
* .what = submit form and wait for success
|
|
131
|
+
* .why = demonstrates error capture wrapper pattern
|
|
132
|
+
*/
|
|
133
|
+
const _action = async (
|
|
134
|
+
input: Record<string, never>,
|
|
135
|
+
context: { page: Page },
|
|
136
|
+
) => {
|
|
137
|
+
await context.page.click('#submit');
|
|
138
|
+
await context.page.waitForURL('**/success');
|
|
139
|
+
return { success: true };
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const action = withErrorSnapshot(_action);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## .captured artifacts
|
|
146
|
+
|
|
147
|
+
| artifact | content | use |
|
|
148
|
+
|----------|---------|-----|
|
|
149
|
+
| screenshot.png | visual state | see what user would see |
|
|
150
|
+
| page.html | dom snapshot | search for elements |
|
|
151
|
+
| console.log | browser console | errors, warnings |
|
|
152
|
+
| network.json | request/response | endpoint failures |
|
|
153
|
+
| storage.json | cookies, localStorage | auth state |
|
|
154
|
+
| error.json | error details | stack trace, context |
|
|
155
|
+
|
|
156
|
+
## .network capture
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface NetworkEntry {
|
|
160
|
+
url: string;
|
|
161
|
+
method?: string;
|
|
162
|
+
status?: number;
|
|
163
|
+
headers: Record<string, string>;
|
|
164
|
+
time: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* .what = create network entry from playwright request
|
|
169
|
+
* .why = pure transformer for request data extraction
|
|
170
|
+
*/
|
|
171
|
+
const asNetworkEntryFromRequest = (input: { req: Request }): NetworkEntry => ({
|
|
172
|
+
url: input.req.url(),
|
|
173
|
+
method: input.req.method(),
|
|
174
|
+
headers: input.req.headers(),
|
|
175
|
+
time: asIsoTimestamp(),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* .what = create network entry from playwright response
|
|
180
|
+
* .why = pure transformer for response data extraction
|
|
181
|
+
*/
|
|
182
|
+
const asNetworkEntryFromResponse = (input: { res: Response }): NetworkEntry => ({
|
|
183
|
+
url: input.res.url(),
|
|
184
|
+
status: input.res.status(),
|
|
185
|
+
headers: input.res.headers(),
|
|
186
|
+
time: asIsoTimestamp(),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* .what = append entry to accumulator array
|
|
191
|
+
* .why = named transformer makes append intent clear without decode
|
|
192
|
+
*/
|
|
193
|
+
const appendToAccumulator = <T>(accumulator: T[], entry: T): void => {
|
|
194
|
+
accumulator.push(entry);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* .what = set up network snapshot capture and return flush function
|
|
199
|
+
* .why = enables capture of request/response data for error diagnosis
|
|
200
|
+
*/
|
|
201
|
+
const setNetworkSnapshotCapture = (input: { page: Page; dir: string }) => {
|
|
202
|
+
// accumulator for network entries - playwright events append via callback
|
|
203
|
+
const entriesAccumulator: NetworkEntry[] = [];
|
|
204
|
+
|
|
205
|
+
// capture request data when fired
|
|
206
|
+
input.page.on('request', (req) => {
|
|
207
|
+
const entry = asNetworkEntryFromRequest({ req });
|
|
208
|
+
appendToAccumulator(entriesAccumulator, entry);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// capture response data when fired
|
|
212
|
+
input.page.on('response', (res) => {
|
|
213
|
+
const entry = asNetworkEntryFromResponse({ res });
|
|
214
|
+
appendToAccumulator(entriesAccumulator, entry);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// return flush function to persist captured data
|
|
218
|
+
return async () => {
|
|
219
|
+
// snapshot entries at flush time (immutable copy)
|
|
220
|
+
const entriesSnapshot = [...entriesAccumulator];
|
|
221
|
+
await fs.writeFile(`${input.dir}/network.json`, JSON.stringify(entriesSnapshot, null, 2));
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## .test integration
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { UnexpectedCodePathError } from 'helpful-errors';
|
|
230
|
+
|
|
231
|
+
// in test setup - capture state on failure for diagnosis
|
|
232
|
+
afterEach(async () => {
|
|
233
|
+
if (testInfo.status !== 'passed') {
|
|
234
|
+
// .note = capture state for post-mortem; original error already propagated via testInfo
|
|
235
|
+
await setErrorSnapshot(
|
|
236
|
+
{
|
|
237
|
+
error: new UnexpectedCodePathError('test failed', {
|
|
238
|
+
testTitle: testInfo.title,
|
|
239
|
+
testFile: testInfo.file,
|
|
240
|
+
originalError: testInfo.error?.message,
|
|
241
|
+
}),
|
|
242
|
+
captureId: null,
|
|
243
|
+
},
|
|
244
|
+
{ page },
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## .see also
|
|
251
|
+
|
|
252
|
+
- `howto.debug-movie-frames.md` — sequential capture
|
|
253
|
+
- `howto.browser-diagnosis.md` — diagnosis workflow
|