testronaut 1.0.25 → 1.0.26
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/package.json +1 -1
- package/tools/chromeBrowser.js +104 -19
package/package.json
CHANGED
package/tools/chromeBrowser.js
CHANGED
|
@@ -504,11 +504,24 @@ export class ChromeBrowser {
|
|
|
504
504
|
throw new Error('Could not input text with any strategy');
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
_chromiumLaunchArgsForCI() {
|
|
508
|
+
const inCI = !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.TESTRONAUT_CI_CHROMIUM);
|
|
509
|
+
return inCI ? ['--no-sandbox', '--disable-setuid-sandbox'] : [];
|
|
510
|
+
}
|
|
511
|
+
|
|
507
512
|
|
|
508
513
|
async start() {
|
|
509
514
|
try {
|
|
510
515
|
const pw = await loadPlaywright();
|
|
511
|
-
|
|
516
|
+
const launchArgs = [
|
|
517
|
+
'--disable-dev-shm-usage',
|
|
518
|
+
'--disable-gpu',
|
|
519
|
+
...this._chromiumLaunchArgsForCI(),
|
|
520
|
+
];
|
|
521
|
+
this.browser = await pw.chromium.launch({
|
|
522
|
+
headless: true,
|
|
523
|
+
args: launchArgs,
|
|
524
|
+
});
|
|
512
525
|
} catch (err) {
|
|
513
526
|
const msg = String(err?.message || err);
|
|
514
527
|
const needsInstall =
|
|
@@ -525,7 +538,16 @@ export class ChromeBrowser {
|
|
|
525
538
|
|
|
526
539
|
// Retry
|
|
527
540
|
const pw = await loadPlaywright();
|
|
528
|
-
|
|
541
|
+
const launchArgs = [
|
|
542
|
+
'--disable-dev-shm-usage',
|
|
543
|
+
'--disable-gpu',
|
|
544
|
+
...this._chromiumLaunchArgsForCI(),
|
|
545
|
+
];
|
|
546
|
+
this.browser = await pw.chromium.launch({
|
|
547
|
+
headless: true,
|
|
548
|
+
args: launchArgs,
|
|
549
|
+
});
|
|
550
|
+
// return await pw.chromium.launch({ headless: true });
|
|
529
551
|
}
|
|
530
552
|
this.context = await this.browser.newContext();
|
|
531
553
|
const first = await this.context.newPage();
|
|
@@ -740,34 +762,97 @@ export class ChromeBrowser {
|
|
|
740
762
|
|
|
741
763
|
|
|
742
764
|
|
|
743
|
-
async get_dom(
|
|
744
|
-
|
|
765
|
+
async get_dom(
|
|
766
|
+
{ limit = 100000, exclude = true, focus = [], loadState = 'networkidle', timeout = 10000 } = {},
|
|
767
|
+
agentMemory = {}
|
|
768
|
+
) {
|
|
769
|
+
// --- 1) Ensure we have a live page ---
|
|
770
|
+
const hasContext = !!this.context;
|
|
771
|
+
|
|
772
|
+
// Recover a page if missing/closed
|
|
773
|
+
const recoverPage = async () => {
|
|
774
|
+
if (hasContext) {
|
|
775
|
+
const pages = (this.context.pages?.() || []).filter(p => !p.isClosed?.());
|
|
776
|
+
if (pages.length) {
|
|
777
|
+
// Prefer the last page (often the active one / popup)
|
|
778
|
+
this.page = pages[pages.length - 1];
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
// No pages at all in a live context -> make one
|
|
782
|
+
this.page = await this.context.newPage();
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
// No context yet but a browser exists -> create context + page
|
|
786
|
+
if (this.browser?.newContext) {
|
|
787
|
+
this.context = await this.browser.newContext();
|
|
788
|
+
this.page = await this.context.newPage();
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
throw new Error('No browser context/page available in get_dom().');
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
if (!this.page || this.page.isClosed?.()) {
|
|
795
|
+
await recoverPage();
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// --- 2) Try to reach a usable load state (best-effort) ---
|
|
745
799
|
try {
|
|
746
|
-
|
|
747
|
-
} catch
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
800
|
+
await this.page.waitForLoadState(loadState, { timeout });
|
|
801
|
+
} catch {
|
|
802
|
+
// Non-fatal: some apps never reach 'load', we’ll still try to read the DOM.
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// If navigation changed active page (e.g., popup), reselect a live page
|
|
806
|
+
try {
|
|
807
|
+
const pages = this.context?.pages?.() || [];
|
|
808
|
+
if (pages.length && (this.page.isClosed?.() || !pages.includes(this.page))) {
|
|
809
|
+
this.page = pages[pages.length - 1];
|
|
810
|
+
}
|
|
811
|
+
} catch { /* ignore */ }
|
|
812
|
+
|
|
813
|
+
// --- 3) Get HTML content with a small retry on failure ---
|
|
814
|
+
let html = '';
|
|
815
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
816
|
+
try {
|
|
817
|
+
html = await this.page.content();
|
|
818
|
+
if (html && html.trim()) break;
|
|
819
|
+
} catch (err) {
|
|
820
|
+
// First failure: try waiting for network to settle, then retry
|
|
821
|
+
if (attempt === 0) {
|
|
822
|
+
try { await this.page.waitForLoadState('networkidle', { timeout: 5000 }); } catch {}
|
|
823
|
+
// Also re-check active page (popups can swap)
|
|
824
|
+
try {
|
|
825
|
+
const pages = this.context?.pages?.() || [];
|
|
826
|
+
if (pages.length) this.page = pages[pages.length - 1];
|
|
827
|
+
} catch {}
|
|
828
|
+
} else {
|
|
829
|
+
// Second failure: bubble up a clearer message
|
|
830
|
+
throw new Error(`get_dom() failed to read page content: ${err?.message || err}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
752
833
|
}
|
|
834
|
+
|
|
835
|
+
// Fallback to empty string if still nothing (keeps caller logic simple)
|
|
836
|
+
if (!html) html = '';
|
|
837
|
+
|
|
838
|
+
// --- 4) Optional DOM reduction/cleanup (your existing steps) ---
|
|
753
839
|
let $ = cheerio.load(html);
|
|
754
|
-
|
|
755
|
-
// Remove non-content tags
|
|
840
|
+
|
|
756
841
|
if (exclude) {
|
|
757
842
|
$ = await removeNonContentTags($);
|
|
758
|
-
// console.log('🔧 Non-content tags removed from DOM.');
|
|
759
843
|
$ = await reduceRepeatedDivs($);
|
|
760
|
-
//
|
|
844
|
+
// If you later re-enable:
|
|
761
845
|
// $ = await removeObfuscatedClassNames($);
|
|
762
|
-
// console.log('🔧 Obfuscated class names removed from DOM.')
|
|
763
846
|
// $ = await removeAutoGeneratedClasses($);
|
|
764
|
-
// console.log('🔧 Auto-generated classes removed from DOM.');
|
|
765
847
|
}
|
|
766
|
-
|
|
848
|
+
|
|
849
|
+
// (Optional: If you'd like to use `focus`, you could filter here by selectors/keywords.)
|
|
850
|
+
|
|
851
|
+
// --- 5) Return trimmed HTML ---
|
|
767
852
|
const finalHtml = $.html();
|
|
768
|
-
|
|
769
|
-
return trimmed;
|
|
853
|
+
return finalHtml.slice(0, limit);
|
|
770
854
|
}
|
|
855
|
+
|
|
771
856
|
|
|
772
857
|
async check_text({ text }) {
|
|
773
858
|
const html = await this.page.content();
|