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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/dist/contract/sdk/index.d.ts +3 -0
  3. package/dist/contract/sdk/index.js +10 -0
  4. package/dist/contract/sdk/index.js.map +1 -0
  5. package/dist/domain.roles/getRoleRegistry.d.ts +6 -0
  6. package/dist/domain.roles/getRoleRegistry.js +17 -0
  7. package/dist/domain.roles/getRoleRegistry.js.map +1 -0
  8. package/dist/domain.roles/inspector/boot.yml +4 -0
  9. package/dist/domain.roles/inspector/getInspectorRole.d.ts +6 -0
  10. package/dist/domain.roles/inspector/getInspectorRole.js +34 -0
  11. package/dist/domain.roles/inspector/getInspectorRole.js.map +1 -0
  12. package/dist/domain.roles/inspector/readme.md +7 -0
  13. package/dist/domain.roles/playwright/boot.yml +37 -0
  14. package/dist/domain.roles/playwright/briefs/auth/howto.cross-session-auth.md +134 -0
  15. package/dist/domain.roles/playwright/briefs/debug/howto.capture-state-on-error.md +253 -0
  16. package/dist/domain.roles/playwright/briefs/debug/howto.debug-movie-frames.md +182 -0
  17. package/dist/domain.roles/playwright/briefs/diagnosis/howto.browser-diagnosis.md +152 -0
  18. package/dist/domain.roles/playwright/briefs/diagnosis/rule.require.grep-html-before-selector-guess.md +82 -0
  19. package/dist/domain.roles/playwright/briefs/diagnosis/rule.require.snapshot-before-assume.md +73 -0
  20. package/dist/domain.roles/playwright/briefs/diagnosis/rule.require.snapshot-before-debug.md +81 -0
  21. package/dist/domain.roles/playwright/briefs/diagnosis/rule.require.snapshot-latest-tab.md +84 -0
  22. package/dist/domain.roles/playwright/briefs/reliability/ref.cdp-reconnection-patterns.md +168 -0
  23. package/dist/domain.roles/playwright/briefs/reliability/rule.forbid.coordinate-clicks.md +53 -0
  24. package/dist/domain.roles/playwright/briefs/reliability/rule.require.bounded-timeouts-and-bisection.md +184 -0
  25. package/dist/domain.roles/playwright/briefs/rule.im_a.bhrowser_lizard.md +68 -0
  26. package/dist/domain.roles/playwright/briefs/skills/howto.browser-action-playbooks.md +265 -0
  27. package/dist/domain.roles/playwright/briefs/skills/howto.browser-byhand-work.md +151 -0
  28. package/dist/domain.roles/playwright/briefs/skills/rule.require.playbooks-over-adhoc.md +90 -0
  29. package/dist/domain.roles/playwright/briefs/spa/rule.require.spa-navigation-fail-fast.md +109 -0
  30. package/dist/domain.roles/playwright/briefs/spa/rule.require.wait-for-content-not-shell.md +128 -0
  31. package/dist/domain.roles/playwright/briefs/spa/rule.require.wait-for-react-render.md +127 -0
  32. package/dist/domain.roles/playwright/briefs/spa/rule.require.wait-for-target-element.md +123 -0
  33. package/dist/domain.roles/playwright/briefs/stealth/ref.antibot-escalation.md +120 -0
  34. package/dist/domain.roles/playwright/briefs/test/howto.test-via-browser.md +285 -0
  35. package/dist/domain.roles/playwright/briefs/test/rule.forbid.afterall-state-mutation.md +106 -0
  36. package/dist/domain.roles/playwright/briefs/test/rule.forbid.test-goto.md +77 -0
  37. package/dist/domain.roles/playwright/briefs/test/rule.require.fast-tests.md +130 -0
  38. package/dist/domain.roles/playwright/briefs/verification/rule.forbid.unverified-actions.md +95 -0
  39. package/dist/domain.roles/playwright/briefs/verification/rule.require.action-verification.md +138 -0
  40. package/dist/domain.roles/playwright/getPlaywrightRole.d.ts +6 -0
  41. package/dist/domain.roles/playwright/getPlaywrightRole.js +34 -0
  42. package/dist/domain.roles/playwright/getPlaywrightRole.js.map +1 -0
  43. package/dist/domain.roles/playwright/readme.md +7 -0
  44. package/dist/domain.roles/playwright/skills/.test/infra/browser.ts +272 -0
  45. package/dist/domain.roles/playwright/skills/browser.action.d.ts +23 -0
  46. package/dist/domain.roles/playwright/skills/browser.action.js +124 -0
  47. package/dist/domain.roles/playwright/skills/browser.action.js.map +1 -0
  48. package/dist/domain.roles/playwright/skills/browser.action.sh +120 -0
  49. package/dist/domain.roles/playwright/skills/browser.action.ts +148 -0
  50. package/dist/domain.roles/playwright/skills/browser.describe.d.ts +15 -0
  51. package/dist/domain.roles/playwright/skills/browser.describe.js +191 -0
  52. package/dist/domain.roles/playwright/skills/browser.describe.js.map +1 -0
  53. package/dist/domain.roles/playwright/skills/browser.describe.sh +45 -0
  54. package/dist/domain.roles/playwright/skills/browser.describe.ts +255 -0
  55. package/dist/domain.roles/playwright/skills/browser.lib.sh +725 -0
  56. package/dist/domain.roles/playwright/skills/browser.session.d.ts +1 -0
  57. package/dist/domain.roles/playwright/skills/browser.session.js +253 -0
  58. package/dist/domain.roles/playwright/skills/browser.session.js.map +1 -0
  59. package/dist/domain.roles/playwright/skills/browser.session.sh +194 -0
  60. package/dist/domain.roles/playwright/skills/browser.session.ts +310 -0
  61. package/dist/domain.roles/playwright/skills/browser.snapshot.console.d.ts +33 -0
  62. package/dist/domain.roles/playwright/skills/browser.snapshot.console.js +163 -0
  63. package/dist/domain.roles/playwright/skills/browser.snapshot.console.js.map +1 -0
  64. package/dist/domain.roles/playwright/skills/browser.snapshot.console.sh +57 -0
  65. package/dist/domain.roles/playwright/skills/browser.snapshot.console.ts +212 -0
  66. package/dist/domain.roles/playwright/skills/browser.snapshot.html.d.ts +17 -0
  67. package/dist/domain.roles/playwright/skills/browser.snapshot.html.js +133 -0
  68. package/dist/domain.roles/playwright/skills/browser.snapshot.html.js.map +1 -0
  69. package/dist/domain.roles/playwright/skills/browser.snapshot.html.sh +53 -0
  70. package/dist/domain.roles/playwright/skills/browser.snapshot.html.ts +152 -0
  71. package/dist/domain.roles/playwright/skills/browser.snapshot.meta.d.ts +28 -0
  72. package/dist/domain.roles/playwright/skills/browser.snapshot.meta.js +109 -0
  73. package/dist/domain.roles/playwright/skills/browser.snapshot.meta.js.map +1 -0
  74. package/dist/domain.roles/playwright/skills/browser.snapshot.meta.sh +53 -0
  75. package/dist/domain.roles/playwright/skills/browser.snapshot.meta.ts +136 -0
  76. package/dist/domain.roles/playwright/skills/browser.snapshot.network.d.ts +28 -0
  77. package/dist/domain.roles/playwright/skills/browser.snapshot.network.js +160 -0
  78. package/dist/domain.roles/playwright/skills/browser.snapshot.network.js.map +1 -0
  79. package/dist/domain.roles/playwright/skills/browser.snapshot.network.sh +57 -0
  80. package/dist/domain.roles/playwright/skills/browser.snapshot.network.ts +188 -0
  81. package/dist/domain.roles/playwright/skills/browser.snapshot.screen.d.ts +17 -0
  82. package/dist/domain.roles/playwright/skills/browser.snapshot.screen.js +138 -0
  83. package/dist/domain.roles/playwright/skills/browser.snapshot.screen.js.map +1 -0
  84. package/dist/domain.roles/playwright/skills/browser.snapshot.screen.sh +65 -0
  85. package/dist/domain.roles/playwright/skills/browser.snapshot.screen.ts +187 -0
  86. package/dist/domain.roles/playwright/skills/browser.snapshot.sh +323 -0
  87. package/dist/domain.roles/playwright/skills/browser.snapshot.storage.d.ts +21 -0
  88. package/dist/domain.roles/playwright/skills/browser.snapshot.storage.js +154 -0
  89. package/dist/domain.roles/playwright/skills/browser.snapshot.storage.js.map +1 -0
  90. package/dist/domain.roles/playwright/skills/browser.snapshot.storage.sh +53 -0
  91. package/dist/domain.roles/playwright/skills/browser.snapshot.storage.ts +172 -0
  92. package/dist/domain.roles/playwright/skills/browser.start.sh +266 -0
  93. package/dist/domain.roles/playwright/skills/browser.stop.sh +89 -0
  94. package/dist/domain.roles/playwright/skills/lib/shared.d.ts +219 -0
  95. package/dist/domain.roles/playwright/skills/lib/shared.js +416 -0
  96. package/dist/domain.roles/playwright/skills/lib/shared.js.map +1 -0
  97. package/dist/domain.roles/playwright/skills/lib/shared.ts +480 -0
  98. package/dist/domain.roles/readme.md +8 -0
  99. package/dist/domain.roles/scraper/boot.yml +8 -0
  100. package/dist/domain.roles/scraper/briefs/cache/howto.cache-browser-scrapes.md +257 -0
  101. package/dist/domain.roles/scraper/briefs/cache/howto.setup-cache-infrastructure.md +62 -0
  102. package/dist/domain.roles/scraper/briefs/cache/ref.remote-state-query-cache.md +43 -0
  103. package/dist/domain.roles/scraper/briefs/cache/rule.require.remote-state-cache.md +119 -0
  104. package/dist/domain.roles/scraper/getScraperRole.d.ts +6 -0
  105. package/dist/domain.roles/scraper/getScraperRole.js +34 -0
  106. package/dist/domain.roles/scraper/getScraperRole.js.map +1 -0
  107. package/dist/domain.roles/scraper/readme.md +7 -0
  108. package/dist/domain.roles/scraper/skills/.test/infra/cache.ts +184 -0
  109. package/dist/domain.roles/scraper/skills/cache.expire.sh +159 -0
  110. package/dist/domain.roles/scraper/skills/cache.extend.sh +416 -0
  111. package/dist/index.d.ts +1 -0
  112. package/dist/index.js +18 -0
  113. package/dist/index.js.map +1 -0
  114. package/license.md +21 -0
  115. package/package.json +116 -0
  116. package/readme.[seed].md +2 -0
  117. package/readme.md +65 -0
  118. 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,3 @@
1
+ export { getRoleRegistry } from '../../domain.roles/getRoleRegistry';
2
+ export { ROLE_INSPECTOR } from '../../domain.roles/inspector/getInspectorRole';
3
+ export { ROLE_PLAYWRIGHT } from '../../domain.roles/playwright/getPlaywrightRole';
@@ -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,6 @@
1
+ import { RoleRegistry } from 'rhachet';
2
+ /**
3
+ * .what = returns the bhrowser registry of predefined roles
4
+ * .why = enables rhachet to discover and enroll bhrowser roles
5
+ */
6
+ export declare const getRoleRegistry: () => RoleRegistry;
@@ -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,4 @@
1
+ always:
2
+ briefs:
3
+ say: []
4
+ ref: []
@@ -0,0 +1,6 @@
1
+ import { Role } from 'rhachet';
2
+ /**
3
+ * .what = returns the inspector role definition
4
+ * .why = enables rhachet to enroll brains with inspector capabilities
5
+ */
6
+ export declare const ROLE_INSPECTOR: Role;
@@ -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