From d63f2adb6f14371deaccb995e23bc7cf88642304 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 18 Jun 2026 01:26:48 -0700 Subject: [PATCH] test(browse): cover readHostProfile clamp, toString depth-3, chrome.* calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-landing review coverage gaps: - readHostProfile clamps 0/negative/NaN/missing env to 8 (a deviceMemory=0 or NaN would be a glaring bot tell) — now asserted. - toString proxy survives the depth-3 recursion trick (fn.toString.toString.toString().includes('[native code]')), the headline claim that was only tested at depth-1. - chrome.csi() and chrome.loadTimes() are invoked (not just typeof-checked) and runtime.connect() throws the native-shaped "No matching signature" error. - AUTOMATION_ARTIFACT_CLEANUP_SCRIPT static shape (cdc_/__webdriver strip + notifications->prompt) as a hermetic backup for the live-Chromium pairing test. Co-Authored-By: Claude Opus 4.8 (1M context) --- browse/test/stealth-layer-c.test.ts | 49 +++++++++++++++++++++++++-- browse/test/stealth-webdriver.test.ts | 41 ++++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/browse/test/stealth-layer-c.test.ts b/browse/test/stealth-layer-c.test.ts index eeeef0b09..db6a4c4d3 100644 --- a/browse/test/stealth-layer-c.test.ts +++ b/browse/test/stealth-layer-c.test.ts @@ -8,10 +8,12 @@ * These tests only exercise the JS script builder + the static export * shapes — fast, hermetic, no chromium launch. */ -import { describe, test, expect } from 'bun:test'; +import { describe, test, expect, afterEach } from 'bun:test'; import { buildStealthScript, buildGStackLaunchArgs, + readHostProfile, + AUTOMATION_ARTIFACT_CLEANUP_SCRIPT, WEBDRIVER_MASK_SCRIPT, STEALTH_LAUNCH_ARGS, STEALTH_IGNORE_DEFAULT_ARGS, @@ -89,7 +91,7 @@ describe('buildStealthScript — T3 Layer C', () => { expect(s).not.toMatch(/return 8;.*hardwareConcurrency/); }); - test('cleans up Selenium 25 globals + Playwright + Phantom + Nightmare', () => { + test('cleans up Selenium + Playwright + Phantom + Nightmare globals', () => { const s = buildStealthScript(hw); // Spot-check a few from each category expect(s).toContain('__webdriver_evaluate'); // Selenium @@ -241,3 +243,46 @@ describe('backwards-compat exports', () => { expect(STEALTH_LAUNCH_ARGS).toContain('--disable-blink-features=AutomationControlled'); }); }); + +describe('readHostProfile — clamp/fallback', () => { + let savedHw: string | undefined; + let savedMem: string | undefined; + afterEach(() => { + if (savedHw === undefined) delete process.env.GSTACK_HW_CONCURRENCY; + else process.env.GSTACK_HW_CONCURRENCY = savedHw; + if (savedMem === undefined) delete process.env.GSTACK_DEVICE_MEMORY; + else process.env.GSTACK_DEVICE_MEMORY = savedMem; + }); + function withHw(hw: string | undefined, mem: string | undefined): ReturnType { + savedHw = process.env.GSTACK_HW_CONCURRENCY; + savedMem = process.env.GSTACK_DEVICE_MEMORY; + if (hw === undefined) delete process.env.GSTACK_HW_CONCURRENCY; else process.env.GSTACK_HW_CONCURRENCY = hw; + if (mem === undefined) delete process.env.GSTACK_DEVICE_MEMORY; else process.env.GSTACK_DEVICE_MEMORY = mem; + return readHostProfile(); + } + + test('valid env values pass through', () => { + expect(withHw('16', '8')).toEqual({ hwConcurrency: 16, deviceMemory: 8 }); + }); + + test('missing env → default 8/8', () => { + expect(withHw(undefined, undefined)).toEqual({ hwConcurrency: 8, deviceMemory: 8 }); + }); + + test('zero / negative / NaN / empty all clamp to 8 (never a 0 or NaN bot tell)', () => { + for (const bad of ['0', '-4', 'abc', '']) { + const p = withHw(bad, bad); + expect(p.hwConcurrency).toBe(8); + expect(p.deviceMemory).toBe(8); + } + }); +}); + +describe('AUTOMATION_ARTIFACT_CLEANUP_SCRIPT — static shape', () => { + test('strips cdc_/__webdriver and maps notifications query to prompt', () => { + expect(AUTOMATION_ARTIFACT_CLEANUP_SCRIPT).toContain("startsWith('cdc_')"); + expect(AUTOMATION_ARTIFACT_CLEANUP_SCRIPT).toContain("startsWith('__webdriver')"); + expect(AUTOMATION_ARTIFACT_CLEANUP_SCRIPT).toContain("name === 'notifications'"); + expect(AUTOMATION_ARTIFACT_CLEANUP_SCRIPT).toContain("state: 'prompt'"); + }); +}); diff --git a/browse/test/stealth-webdriver.test.ts b/browse/test/stealth-webdriver.test.ts index 084229e09..9ef595cb9 100644 --- a/browse/test/stealth-webdriver.test.ts +++ b/browse/test/stealth-webdriver.test.ts @@ -162,6 +162,47 @@ describe('applyStealth — context level', () => { await page.close(); } }); + + test('toString proxy survives the depth-3 recursion trick', async () => { + // The headline claim: defeats fn.toString.toString.toString().includes( + // '[native code]'). Depth-1 is covered above; this walks the full chain a + // detector uses so a regression that only masks one level is caught. + const page = await context.newPage(); + try { + const depth3 = await page.evaluate(() => { + const wd = Object.getOwnPropertyDescriptor(navigator, 'webdriver'); + const get = wd && wd.get; + return get ? (get as any).toString.toString.toString().includes('[native code]') : false; + }); + expect(depth3).toBe(true); + } finally { + await page.close(); + } + }); + + test('chrome.csi() and chrome.loadTimes() execute, runtime.connect() throws native-shaped', async () => { + // Presence (typeof === 'function') is not enough — a real detector calls + // them. loadTimes() dereferences performance.timing; connect() must throw + // the native "No matching signature" TypeError. + const page = await context.newPage(); + try { + const r = await page.evaluate(() => { + const c = (window as any).chrome; + let connectErr = ''; + try { c.runtime.connect(); } catch (e) { connectErr = String(e); } + return { + csiOk: typeof c.csi().onloadT === 'number', + loadTimesOk: typeof c.loadTimes().wasFetchedViaSpdy === 'boolean', + connectErr, + }; + }); + expect(r.csiOk).toBe(true); + expect(r.loadTimesOk).toBe(true); + expect(r.connectErr).toContain('No matching signature'); + } finally { + await page.close(); + } + }); }); describe('applyStealth — per-install hardware from env', () => {