@duckduckgo/autoconsent 16.43.2 → 16.44.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/.agents/lib/regional-testing/harness.mjs +536 -0
- package/.agents/skills/oxylabs-testing/SKILL.md +99 -0
- package/.agents/skills/oxylabs-testing/scripts/oxylabs.mjs +300 -0
- package/.agents/skills/oxylabs-testing/scripts/oxylabs.test.mjs +37 -0
- package/.agents/skills/proxy-testing/SKILL.md +5 -2
- package/.agents/skills/proxy-testing/scripts/regional-proxy.mjs +22 -471
- package/.claude/skills/oxylabs-testing/SKILL.md +6 -0
- package/.claude/skills/proxy-testing/SKILL.md +1 -1
- package/.claude/user-settings.json.TEMPLATE +2 -0
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +29 -0
- package/dist/addon-firefox/background.bundle.js +9 -3
- package/dist/addon-firefox/compact-rules.json +1 -1
- package/dist/addon-firefox/manifest.json +1 -1
- package/dist/addon-firefox/popup.bundle.js +9 -3
- package/dist/addon-firefox/rule-index.json +1 -1
- package/dist/addon-firefox/rules.json +1 -1
- package/dist/addon-mv3/background.bundle.js +9 -3
- package/dist/addon-mv3/compact-rules.json +1 -1
- package/dist/addon-mv3/manifest.json +1 -1
- package/dist/addon-mv3/popup.bundle.js +9 -3
- package/dist/addon-mv3/rule-index.json +1 -1
- package/dist/addon-mv3/rules.json +1 -1
- package/dist/autoconsent.standalone.js +1 -1
- package/package.json +2 -2
- package/readme.md +1 -1
- package/rules/autoconsent/bandcamp.json +42 -3
- package/rules/autoconsent/chatgpt.json +2 -2
- package/rules/autoconsent/elsevier.json +69 -0
- package/rules/autoconsent/fides.json +10 -2
- package/rules/autoconsent/lacoccinelle.json +97 -0
- package/rules/autoconsent/woo-commerce-com.json +28 -11
- package/rules/compact-rules.json +1 -1
- package/rules/rule-index.json +1 -1
- package/rules/rules.json +1 -1
- package/tests/bandcamp.spec.ts +7 -1
- package/tests/elsevier.spec.ts +3 -0
- package/tests/fides.spec.ts +7 -1
- package/tests/lacoccinelle.spec.ts +3 -0
- package/tests/woo-commerce-com.spec.ts +4 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Oxylabs Agent Browser utilities for multi-region autoconsent testing.
|
|
3
|
+
* Docs: https://developers.oxylabs.io/products/agent-browser
|
|
4
|
+
*
|
|
5
|
+
* Connects to Oxylabs CDP browsers, injects autoconsent into an isolated world, and evaluates
|
|
6
|
+
* opt-out/opt-in flows across geographic regions. For sites that show our regional proxies a
|
|
7
|
+
* bot wall; the `proxy-testing` skill is the default.
|
|
8
|
+
*
|
|
9
|
+
* Requires env vars: OXYLABS_USER, OXYLABS_PASSWORD.
|
|
10
|
+
* Optional: OXYLABS_HOST (defaults to hb.oxylabs.io).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {import('playwright').Page} Page
|
|
15
|
+
* @typedef {import('playwright').Browser} Browser
|
|
16
|
+
* @typedef {import('../../../lib/regional-testing/harness.mjs').TestResult} TestResult
|
|
17
|
+
* @typedef {import('../../../lib/regional-testing/harness.mjs').AutoconsentContext} AutoconsentContext
|
|
18
|
+
* @typedef {import('../../../lib/regional-testing/harness.mjs').Provider} Provider
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {'desktop'|'mobile'} OxylabsDevice
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef {Object} OxylabsBrowserOptions
|
|
27
|
+
* @property {OxylabsDevice} [device] - Oxylabs ?p_device= value. Oxylabs defaults to 'desktop'.
|
|
28
|
+
* @property {boolean} [solveCaptcha=false] - Send ?solve_captcha=true: let Oxylabs solve captchas on the page. Needs the feature enabled on the account.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {import('../../../lib/regional-testing/harness.mjs').TestOptions & OxylabsBrowserOptions} TestOptions
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {AutoconsentContext & {
|
|
37
|
+
* captcha: { detected: boolean, solved: boolean|null },
|
|
38
|
+
* captchaPromise: Promise<void>,
|
|
39
|
+
* }} OxylabsAutoconsentContext - `captcha` reflects the Oxylabs solver state; `captchaPromise` resolves when solving ends (success or error). With `solveCaptcha`, `waitForCompletion` pauses while a captcha is being solved.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import path from 'path';
|
|
43
|
+
import { chromium } from 'playwright';
|
|
44
|
+
import {
|
|
45
|
+
CORE_REGIONS,
|
|
46
|
+
emptyResult,
|
|
47
|
+
injectAutoconsent as injectThrough,
|
|
48
|
+
projectRoot,
|
|
49
|
+
runTest,
|
|
50
|
+
} from '../../../lib/regional-testing/harness.mjs';
|
|
51
|
+
|
|
52
|
+
export { ALL_REGIONS, CORE_REGIONS, EXPANDED_REGIONS, formatResult } from '../../../lib/regional-testing/harness.mjs';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* US state- and city-targeted region keys, in addition to any two-letter country code. Query params
|
|
56
|
+
* follow https://developers.oxylabs.io/products/agent-browser/geolocation-and-proxy-selection
|
|
57
|
+
* (`p_state` is US-only; `p_city` needs `p_cc` or `p_state`).
|
|
58
|
+
* @type {Record<string, Record<string, string>>}
|
|
59
|
+
*/
|
|
60
|
+
export const OXYLABS_LOCAL_REGIONS = {
|
|
61
|
+
'us-california': { p_state: 'california' },
|
|
62
|
+
'us-newyork': { p_cc: 'US', p_city: 'new_york' },
|
|
63
|
+
'us-losangeles': { p_cc: 'US', p_city: 'los_angeles' },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} regionKey - A two-letter country code (e.g. 'de', 'pt') or a key of OXYLABS_LOCAL_REGIONS.
|
|
68
|
+
* @returns {Record<string, string>}
|
|
69
|
+
*/
|
|
70
|
+
export function oxylabsRegionParams(regionKey) {
|
|
71
|
+
if (Object.prototype.hasOwnProperty.call(OXYLABS_LOCAL_REGIONS, regionKey)) {
|
|
72
|
+
return OXYLABS_LOCAL_REGIONS[regionKey];
|
|
73
|
+
}
|
|
74
|
+
if (/^[a-z]{2}$/i.test(regionKey)) {
|
|
75
|
+
return { p_cc: regionKey.toUpperCase() };
|
|
76
|
+
}
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Unknown Oxylabs region "${regionKey}". Use a two-letter country code or one of: ${Object.keys(OXYLABS_LOCAL_REGIONS).join(', ')}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
84
|
+
*/
|
|
85
|
+
export function isOxylabsConfigured(env = process.env) {
|
|
86
|
+
return Boolean(env.OXYLABS_USER && env.OXYLABS_PASSWORD);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build the Oxylabs CDP endpoint for a region. The result contains credentials: never log it.
|
|
91
|
+
* @param {string} regionKey
|
|
92
|
+
* @param {OxylabsBrowserOptions} [opts]
|
|
93
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
94
|
+
*/
|
|
95
|
+
export function buildOxylabsEndpoint(regionKey, opts = {}, env = process.env) {
|
|
96
|
+
const user = env.OXYLABS_USER;
|
|
97
|
+
const password = env.OXYLABS_PASSWORD;
|
|
98
|
+
const host = env.OXYLABS_HOST || 'hb.oxylabs.io';
|
|
99
|
+
if (!user || !password) {
|
|
100
|
+
throw new Error('Missing Oxylabs credentials. Set OXYLABS_USER and OXYLABS_PASSWORD.');
|
|
101
|
+
}
|
|
102
|
+
const params = new URLSearchParams(oxylabsRegionParams(regionKey));
|
|
103
|
+
if (opts.device) {
|
|
104
|
+
params.set('p_device', opts.device);
|
|
105
|
+
}
|
|
106
|
+
// Accounts without captcha solving get HTTP 400 for this param, so only send it when asked.
|
|
107
|
+
if (opts.solveCaptcha) {
|
|
108
|
+
params.set('solve_captcha', 'true');
|
|
109
|
+
}
|
|
110
|
+
// user/password are interpolated raw — Oxylabs expects literal characters in
|
|
111
|
+
// userinfo (e.g. '+' must NOT be percent-encoded), matching their Python/JS
|
|
112
|
+
// SDK examples. If your credentials contain ':' or '@', escape upstream.
|
|
113
|
+
return `wss://${user}:${password}@${host}/?${params.toString()}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Playwright puts the endpoint URL in connection error messages; strip the credentials.
|
|
118
|
+
* @param {string} message
|
|
119
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
120
|
+
*/
|
|
121
|
+
export function redactCredentials(message, env = process.env) {
|
|
122
|
+
let out = message;
|
|
123
|
+
for (const secret of [env.OXYLABS_PASSWORD, env.OXYLABS_USER]) {
|
|
124
|
+
if (secret) out = out.split(secret).join('***');
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @param {TestResult} result
|
|
131
|
+
* @returns {TestResult}
|
|
132
|
+
*/
|
|
133
|
+
function redactResult(result) {
|
|
134
|
+
result.errors = result.errors.map((e) => redactCredentials(e));
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Connect to an Oxylabs remote browser for a specific region.
|
|
140
|
+
* @param {string} regionKey
|
|
141
|
+
* @param {OxylabsBrowserOptions} [opts]
|
|
142
|
+
* @returns {Promise<Browser>}
|
|
143
|
+
*/
|
|
144
|
+
export async function connectOxylabs(regionKey, opts = {}) {
|
|
145
|
+
try {
|
|
146
|
+
return await chromium.connectOverCDP(buildOxylabsEndpoint(regionKey, opts), { timeout: 30000 });
|
|
147
|
+
} catch (e) {
|
|
148
|
+
// The first line has the cause; the call log after it repeats the endpoint URL.
|
|
149
|
+
const message = redactCredentials((e instanceof Error ? e.message : String(e)).split('\n')[0]);
|
|
150
|
+
const hint = opts.solveCaptcha && message.includes('400') ? ' (is captcha solving enabled for this Oxylabs account?)' : '';
|
|
151
|
+
throw new Error(message + hint);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Captcha events arrive as window messages from the Oxylabs runtime. A listener in autoconsent's
|
|
156
|
+
// isolated world forwards them over its CDP binding: Agent Browser drops main-world bindings, so
|
|
157
|
+
// page.exposeBinding can't deliver them. See https://developers.oxylabs.io/products/agent-browser/captcha-handling
|
|
158
|
+
const CAPTCHA_MESSAGE = 'oxylabsCaptcha';
|
|
159
|
+
// Agent Browser sends `oxylabs-captcha-solve-start`; the docs list `-start`, `-end`, `-solve-end` and `-error`.
|
|
160
|
+
const CAPTCHA_START_EVENTS = ['oxylabs-captcha-start', 'oxylabs-captcha-solve-start'];
|
|
161
|
+
const CAPTCHA_END_EVENTS = ['oxylabs-captcha-end', 'oxylabs-captcha-solve-end'];
|
|
162
|
+
const CAPTCHA_ERROR_EVENTS = ['oxylabs-captcha-error', 'oxylabs-captcha-solve-error'];
|
|
163
|
+
// Longest a test pauses for solving, counted from the first start event: the wait in Oxylabs' example.
|
|
164
|
+
const CAPTCHA_SOLVE_TIMEOUT_MS = 60000;
|
|
165
|
+
const captchaListenerScript = `
|
|
166
|
+
window.addEventListener("message", (e) => {
|
|
167
|
+
if (e?.data?.source === "oxylabs-runtime" && typeof e.data.type === "string") {
|
|
168
|
+
window.autoconsentSendMessage({ type: "${CAPTCHA_MESSAGE}", event: e.data.type });
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
`;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Inject autoconsent into a page's isolated world via CDP and, with `solveCaptcha`, forward
|
|
175
|
+
* Oxylabs captcha events to the Node side. Call BEFORE navigating to the target URL.
|
|
176
|
+
* @param {Page} page
|
|
177
|
+
* @param {Partial<TestOptions>} [options]
|
|
178
|
+
* @returns {Promise<OxylabsAutoconsentContext>}
|
|
179
|
+
*/
|
|
180
|
+
export async function injectAutoconsent(page, options = {}) {
|
|
181
|
+
/** @type {{ detected: boolean, solved: boolean|null }} */
|
|
182
|
+
const captcha = { detected: false, solved: null };
|
|
183
|
+
/** @type {() => void} */
|
|
184
|
+
let resolveCaptcha = () => {};
|
|
185
|
+
/** @type {Promise<void>} */
|
|
186
|
+
const captchaPromise = new Promise((resolve) => {
|
|
187
|
+
resolveCaptcha = resolve;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
let solving = false;
|
|
191
|
+
/** @type {number|null} */
|
|
192
|
+
let firstStartAt = null;
|
|
193
|
+
|
|
194
|
+
function handleCaptchaEvent(/** @type {string} */ type) {
|
|
195
|
+
if (CAPTCHA_START_EVENTS.includes(type)) {
|
|
196
|
+
captcha.detected = true;
|
|
197
|
+
solving = true;
|
|
198
|
+
firstStartAt ??= Date.now();
|
|
199
|
+
} else if (CAPTCHA_END_EVENTS.includes(type)) {
|
|
200
|
+
captcha.detected = true;
|
|
201
|
+
captcha.solved = true;
|
|
202
|
+
solving = false;
|
|
203
|
+
resolveCaptcha();
|
|
204
|
+
} else if (CAPTCHA_ERROR_EVENTS.includes(type)) {
|
|
205
|
+
captcha.detected = true;
|
|
206
|
+
captcha.solved = false;
|
|
207
|
+
solving = false;
|
|
208
|
+
resolveCaptcha();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Oxylabs gives no deadline for a start event, so autoconsent's wait pauses whenever one arrives.
|
|
213
|
+
const isSolving = () => solving && firstStartAt !== null && Date.now() - firstStartAt < CAPTCHA_SOLVE_TIMEOUT_MS;
|
|
214
|
+
|
|
215
|
+
/** @type {import('../../../lib/regional-testing/harness.mjs').IsolatedWorldExtension} */
|
|
216
|
+
const captchaListener = {
|
|
217
|
+
script: captchaListenerScript,
|
|
218
|
+
onMessage: (msg) => {
|
|
219
|
+
if (msg?.type === CAPTCHA_MESSAGE) handleCaptchaEvent(msg.event);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
const ctx = await injectThrough(page, options, 'oxylabs', options.solveCaptcha ? captchaListener : {});
|
|
223
|
+
return {
|
|
224
|
+
...ctx,
|
|
225
|
+
waitForCompletion: (timeout, detectionTimeout) => ctx.waitForCompletion(timeout, detectionTimeout, isSolving),
|
|
226
|
+
captcha,
|
|
227
|
+
captchaPromise,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** @type {Provider} */
|
|
232
|
+
const provider = {
|
|
233
|
+
name: 'oxylabs',
|
|
234
|
+
defaultScreenshotsDir: path.join(projectRoot, 'test-results/oxylabs'),
|
|
235
|
+
screenshotTag: 'oxylabs',
|
|
236
|
+
inject: injectAutoconsent,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Test a URL using an already-connected page.
|
|
241
|
+
* @param {Page} page
|
|
242
|
+
* @param {string} url
|
|
243
|
+
* @param {string} regionKey
|
|
244
|
+
* @param {Partial<TestOptions>} [options]
|
|
245
|
+
* @returns {Promise<TestResult>}
|
|
246
|
+
*/
|
|
247
|
+
export async function testPage(page, url, regionKey, options = {}) {
|
|
248
|
+
return redactResult(await runTest(page, url, regionKey, options, provider));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Run `fn` on a page of a fresh Oxylabs session for a region, then close the session. Throws if
|
|
253
|
+
* the session cannot be opened.
|
|
254
|
+
* @template T
|
|
255
|
+
* @param {string} regionKey
|
|
256
|
+
* @param {OxylabsBrowserOptions} options
|
|
257
|
+
* @param {(page: Page) => Promise<T>} fn
|
|
258
|
+
* @returns {Promise<T>}
|
|
259
|
+
*/
|
|
260
|
+
export async function withOxylabsPage(regionKey, options, fn) {
|
|
261
|
+
const browser = await connectOxylabs(regionKey, { device: options.device, solveCaptcha: options.solveCaptcha });
|
|
262
|
+
try {
|
|
263
|
+
return await fn(await browser.newPage());
|
|
264
|
+
} finally {
|
|
265
|
+
try {
|
|
266
|
+
await browser.close();
|
|
267
|
+
} catch {}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* High-level: test a URL in a specific region.
|
|
273
|
+
* Connects to Oxylabs, injects autoconsent, waits for results, screenshots, and closes.
|
|
274
|
+
* @param {string} url
|
|
275
|
+
* @param {string} regionKey
|
|
276
|
+
* @param {Partial<TestOptions>} [options]
|
|
277
|
+
* @returns {Promise<TestResult>}
|
|
278
|
+
*/
|
|
279
|
+
export async function testUrl(url, regionKey, options = {}) {
|
|
280
|
+
try {
|
|
281
|
+
return await withOxylabsPage(regionKey, options, (page) => testPage(page, url, regionKey, options));
|
|
282
|
+
} catch (e) {
|
|
283
|
+
return redactResult(emptyResult(url, regionKey, 'oxylabs', e));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Test one URL across several regions, a fresh Oxylabs session per region.
|
|
289
|
+
* @param {string} url
|
|
290
|
+
* @param {string[]} [regions] Defaults to the core pass.
|
|
291
|
+
* @param {Partial<TestOptions>} [options]
|
|
292
|
+
* @returns {Promise<TestResult[]>}
|
|
293
|
+
*/
|
|
294
|
+
export async function testRegions(url, regions = CORE_REGIONS, options = {}) {
|
|
295
|
+
const results = [];
|
|
296
|
+
for (const region of regions) {
|
|
297
|
+
results.push(await testUrl(url, region, options));
|
|
298
|
+
}
|
|
299
|
+
return results;
|
|
300
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { buildOxylabsEndpoint, isOxylabsConfigured, oxylabsRegionParams, redactCredentials } from './oxylabs.mjs';
|
|
5
|
+
|
|
6
|
+
const env = { OXYLABS_USER: 'user', OXYLABS_PASSWORD: 'p+ss' };
|
|
7
|
+
|
|
8
|
+
describe('oxylabs config', () => {
|
|
9
|
+
it('maps two-letter region codes to countries', () => {
|
|
10
|
+
assert.deepEqual(oxylabsRegionParams('gb'), { p_cc: 'GB' });
|
|
11
|
+
assert.deepEqual(oxylabsRegionParams('pt'), { p_cc: 'PT' });
|
|
12
|
+
assert.deepEqual(oxylabsRegionParams('us-california'), { p_state: 'california' });
|
|
13
|
+
assert.deepEqual(oxylabsRegionParams('us-losangeles'), { p_cc: 'US', p_city: 'los_angeles' });
|
|
14
|
+
assert.throws(() => oxylabsRegionParams('europe'), /Unknown Oxylabs region/);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('builds the endpoint with raw credentials and query params', () => {
|
|
18
|
+
assert.equal(buildOxylabsEndpoint('de', { device: 'mobile' }, env), 'wss://user:p+ss@hb.oxylabs.io/?p_cc=DE&p_device=mobile');
|
|
19
|
+
assert.equal(
|
|
20
|
+
buildOxylabsEndpoint('us', { solveCaptcha: true }, { ...env, OXYLABS_HOST: 'proxy.example' }),
|
|
21
|
+
'wss://user:p+ss@proxy.example/?p_cc=US&solve_captcha=true',
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('requires credentials', () => {
|
|
26
|
+
assert.equal(isOxylabsConfigured({}), false);
|
|
27
|
+
assert.equal(isOxylabsConfigured(env), true);
|
|
28
|
+
assert.throws(() => buildOxylabsEndpoint('de', {}, {}), /Missing Oxylabs credentials/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('redacts credentials from error messages', () => {
|
|
32
|
+
assert.equal(
|
|
33
|
+
redactCredentials('WebSocket error: wss://user:p+ss@hb.oxylabs.io/?p_cc=DE 401 Unauthorized', env),
|
|
34
|
+
'WebSocket error: wss://***:***@hb.oxylabs.io/?p_cc=DE 401 Unauthorized',
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -4,7 +4,9 @@ description: Test autoconsent via regional proxies in Playwright. Use when verif
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Proxy Testing
|
|
7
|
-
This skill provides a JS library to run autoconsent via regional proxies in Playwright.
|
|
7
|
+
This skill provides a JS library to run autoconsent via regional proxies in Playwright. It is the default for regional testing.
|
|
8
|
+
|
|
9
|
+
Oxylabs remote browsers are available too, through the `oxylabs-testing` skill. They get past most bot walls (captchas, challenge pages, "access denied" pages) that some sites show to our proxies, but they cost money per session, so use them only for the regions where our proxies don't get you the real site.
|
|
8
10
|
|
|
9
11
|
Rule changes are verified with a two-level regional policy.
|
|
10
12
|
The **core region set** (default for detection, iteration, and verification) covers `us` (CCPA), `gb` (UK GDPR), `de` (EEA GDPR), plus the reported region from the task when specified (if the reported region is not a supported proxy region, use the closest supported one, e.g. `es` for `pt`). Iterate in the single most relevant region; run the full core set once when the fix is stable.
|
|
@@ -131,4 +133,5 @@ await browser.close();
|
|
|
131
133
|
- The content script runs in an isolated world (via CDP) and `eval` snippets run in the page's main world, matching the extension. Chromium only.
|
|
132
134
|
- Use a fresh browser per region to avoid leaking proxy state, cookies, cache, or DNS.
|
|
133
135
|
- Some sites localize by more than IP; only add locale/geolocation settings intentionally.
|
|
134
|
-
- Some sites block proxy traffic outright (captcha, 403, endless challenge page).
|
|
136
|
+
- Some sites block proxy traffic outright (captcha, 403, endless challenge page). Retest the affected regions with the `oxylabs-testing` skill, and say in the report which regions were tested through Oxylabs. If Oxylabs is blocked too or not available, retest over a direct connection and state explicitly in the report that the result is unproxied. If direct access is blocked too, ask the triager for the popup's HTML pasted from their own browser as a last resort, and note that the rule was written from pasted markup rather than a live run.
|
|
137
|
+
- The harness shared with `oxylabs-testing` lives in `.agents/lib/regional-testing/`; change shared behavior there.
|