testaro 60.1.0 → 60.2.1
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/UPGRADES.md +349 -0
- package/package.json +1 -1
- package/run.js +27 -9
package/UPGRADES.md
CHANGED
|
@@ -2453,3 +2453,352 @@ describe('Database queries', () => {
|
|
|
2453
2453
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
2454
2454
|
SOFTWARE.
|
|
2455
2455
|
*/
|
|
2456
|
+
|
|
2457
|
+
## Performance improvements
|
|
2458
|
+
|
|
2459
|
+
### Browser-Node iteration prevention
|
|
2460
|
+
|
|
2461
|
+
It is suspected that some Testaro tests are unnecessarily inefficient because they iterate across elements in a page, performing a browser operation on each and then returning a result for processing by Node. For example, in `lineHeight`:
|
|
2462
|
+
|
|
2463
|
+
```
|
|
2464
|
+
const all = await init(100, page, 'body *', {hasText: /[^\s]/});
|
|
2465
|
+
// For each locator:
|
|
2466
|
+
for (const loc of all.allLocs) {
|
|
2467
|
+
// Get whether its element violates the rule.
|
|
2468
|
+
const data = await loc.evaluate(el => {
|
|
2469
|
+
const styleDec = window.getComputedStyle(el);
|
|
2470
|
+
const {fontSize, lineHeight} = styleDec;
|
|
2471
|
+
return {
|
|
2472
|
+
fontSize: Number.parseFloat(fontSize),
|
|
2473
|
+
lineHeight: Number.parseFloat(lineHeight)
|
|
2474
|
+
};
|
|
2475
|
+
});
|
|
2476
|
+
```
|
|
2477
|
+
|
|
2478
|
+
The inefficiency has necessitating sampling.
|
|
2479
|
+
|
|
2480
|
+
However, there is evidence that moving the processing of Playwright locators into the browser and returning the final result to Node could be 1 or 2 orders of magnitude faster and eliminate the need to sample, thereby improving speed and delivering accuracy and consistency.
|
|
2481
|
+
|
|
2482
|
+
One proposal for a rewrite of `lineHeight`, still doing sampling, was:
|
|
2483
|
+
|
|
2484
|
+
```
|
|
2485
|
+
// Reports a test.
|
|
2486
|
+
exports.reporter = async (page, report) => {
|
|
2487
|
+
// Get data on elements with potentially invalid line heights.
|
|
2488
|
+
const data = await page.evaluate(() => {
|
|
2489
|
+
const instances = [];
|
|
2490
|
+
// Get all visible elements in the body with any text.
|
|
2491
|
+
const elements = Array
|
|
2492
|
+
.from(document.querySelectorAll('body *'))
|
|
2493
|
+
.filter(el => {
|
|
2494
|
+
const styleDec = window.getComputedStyle(el);
|
|
2495
|
+
const {display, visibility} = styleDec;
|
|
2496
|
+
return display !== 'none' && visibility !== 'hidden' && el.textContent.trim().length;
|
|
2497
|
+
});
|
|
2498
|
+
// Get a sample of them.
|
|
2499
|
+
const sampleIndexes = window.getSample(elements, 100);
|
|
2500
|
+
const sample = sampleIndexes.map(index => elements[index]);
|
|
2501
|
+
// For each element in the sample:
|
|
2502
|
+
sample.forEach(el => {
|
|
2503
|
+
const styleDec = window.getComputedStyle(el);
|
|
2504
|
+
const {lineHeight, fontSize} = styleDec;
|
|
2505
|
+
// If the line height is absolute:
|
|
2506
|
+
if (['px', 'pt'].some(unit => lineHeight.endsWith(unit))) {
|
|
2507
|
+
const lhValue = parseFloat(lineHeight);
|
|
2508
|
+
const fsValue = parseFloat(fontSize);
|
|
2509
|
+
// If the line height is less than 1.2 times the font size:
|
|
2510
|
+
if (fsValue > 0 && lhValue / fsValue < 1.2) {
|
|
2511
|
+
const box = el.getBoundingClientRect();
|
|
2512
|
+
// Add an instance to the result.
|
|
2513
|
+
instances.push({
|
|
2514
|
+
ruleID: 'lineHeight',
|
|
2515
|
+
what: 'Line height is less than 1.2 times the font size',
|
|
2516
|
+
ordinalSeverity: 2,
|
|
2517
|
+
tagName: el.tagName,
|
|
2518
|
+
id: el.id || '',
|
|
2519
|
+
location: {
|
|
2520
|
+
doc: 'dom',
|
|
2521
|
+
type: 'box',
|
|
2522
|
+
spec: {
|
|
2523
|
+
x: box.x,
|
|
2524
|
+
y: box.y,
|
|
2525
|
+
width: box.width,
|
|
2526
|
+
height: box.height
|
|
2527
|
+
}
|
|
2528
|
+
},
|
|
2529
|
+
excerpt: el.textContent.trim().replace(/\s+/g, ' ').slice(0, 100)
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
});
|
|
2534
|
+
return {
|
|
2535
|
+
totals: [0, 0, instances.length, 0],
|
|
2536
|
+
instances
|
|
2537
|
+
};
|
|
2538
|
+
});
|
|
2539
|
+
|
|
2540
|
+
// Add the result to the report.
|
|
2541
|
+
try {
|
|
2542
|
+
report.acts[report.actIndex].result.data = data;
|
|
2543
|
+
}
|
|
2544
|
+
catch(error) {
|
|
2545
|
+
console.log(`ERROR: Could not add result to report (${error.message})`);
|
|
2546
|
+
}
|
|
2547
|
+
};
|
|
2548
|
+
```
|
|
2549
|
+
|
|
2550
|
+
To reuse a sampling (or any other) function in multiple tests, it was proposed to store the function in a file and then inject it into the page as follows:
|
|
2551
|
+
|
|
2552
|
+
```
|
|
2553
|
+
const getSample = require(path.join(__dirname, 'procs/sample.js')).getSample;
|
|
2554
|
+
const initScript = `window.getSample = ${getSample.toString()};`;
|
|
2555
|
+
await browserContext.addInitScript(initScript);
|
|
2556
|
+
```
|
|
2557
|
+
|
|
2558
|
+
## Version management for service
|
|
2559
|
+
|
|
2560
|
+
Proposals:
|
|
2561
|
+
|
|
2562
|
+
Prevent Future Conflicts
|
|
2563
|
+
Option 1: Don't commit package-lock.json on the server (Recommended)
|
|
2564
|
+
|
|
2565
|
+
On the server, tell git to ignore local changes to package-lock.json:
|
|
2566
|
+
|
|
2567
|
+
```
|
|
2568
|
+
# Assume package-lock.json is unchanged locally
|
|
2569
|
+
git update-index --assume-unchanged package-lock.json
|
|
2570
|
+
```
|
|
2571
|
+
|
|
2572
|
+
This prevents git from seeing the file as modified when npm update changes it.
|
|
2573
|
+
|
|
2574
|
+
Option 2: Exclude package-lock.json from version control
|
|
2575
|
+
|
|
2576
|
+
If you don't need package-lock.json in your repo (since you use * for all versions in package.json), add it to .gitignore:
|
|
2577
|
+
|
|
2578
|
+
```
|
|
2579
|
+
echo "package-lock.json" >> .gitignore
|
|
2580
|
+
git rm --cached package-lock.json
|
|
2581
|
+
git commit -m "Stop tracking package-lock.json"
|
|
2582
|
+
git push
|
|
2583
|
+
```
|
|
2584
|
+
|
|
2585
|
+
Then on the server:
|
|
2586
|
+
|
|
2587
|
+
```
|
|
2588
|
+
git pull
|
|
2589
|
+
npm update # Will create package-lock.json locally but git won't track it
|
|
2590
|
+
```
|
|
2591
|
+
|
|
2592
|
+
Option 3: Proper deployment workflow (Best Practice)
|
|
2593
|
+
|
|
2594
|
+
On the server, use a deployment script instead of git pull:
|
|
2595
|
+
|
|
2596
|
+
```
|
|
2597
|
+
#!/bin/bash
|
|
2598
|
+
# deploy.sh
|
|
2599
|
+
|
|
2600
|
+
# Stash any local changes
|
|
2601
|
+
git stash
|
|
2602
|
+
|
|
2603
|
+
# Fetch latest from origin
|
|
2604
|
+
git fetch origin
|
|
2605
|
+
|
|
2606
|
+
# Reset to origin/main (discard all local changes)
|
|
2607
|
+
git reset --hard origin/main
|
|
2608
|
+
|
|
2609
|
+
# Clean untracked files
|
|
2610
|
+
git clean -fd
|
|
2611
|
+
|
|
2612
|
+
# Install dependencies
|
|
2613
|
+
npm ci # Use 'ci' instead of 'update' - it's deterministic
|
|
2614
|
+
|
|
2615
|
+
# Restart app
|
|
2616
|
+
pm2 restart kilotest
|
|
2617
|
+
```
|
|
2618
|
+
|
|
2619
|
+
Make it executable and use it:
|
|
2620
|
+
|
|
2621
|
+
```
|
|
2622
|
+
chmod +x deploy.sh
|
|
2623
|
+
./deploy.sh
|
|
2624
|
+
```
|
|
2625
|
+
|
|
2626
|
+
Why This Happens
|
|
2627
|
+
|
|
2628
|
+
With "testaro": "*" in your package.json:
|
|
2629
|
+
|
|
2630
|
+
- Local dev: npm update locks to version X in package-lock.json
|
|
2631
|
+
- Server: npm update locks to version Y (newer) in package-lock.json
|
|
2632
|
+
- Git: Sees conflicting versions when you pull
|
|
2633
|
+
|
|
2634
|
+
Recommended Approach
|
|
2635
|
+
|
|
2636
|
+
Use exact versions to ensure consistency across environments:
|
|
2637
|
+
|
|
2638
|
+
```
|
|
2639
|
+
{
|
|
2640
|
+
"dependencies": {
|
|
2641
|
+
"dotenv": "^17.2.3",
|
|
2642
|
+
"testaro": "^2.1.0",
|
|
2643
|
+
"testilo": "^1.5.0"
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
```
|
|
2647
|
+
|
|
2648
|
+
Then:
|
|
2649
|
+
|
|
2650
|
+
```
|
|
2651
|
+
# Lock versions
|
|
2652
|
+
npm install
|
|
2653
|
+
|
|
2654
|
+
# Commit the lock file
|
|
2655
|
+
git add package.json package-lock.json
|
|
2656
|
+
git commit -m "Lock dependency versions"
|
|
2657
|
+
git push
|
|
2658
|
+
|
|
2659
|
+
# On server
|
|
2660
|
+
git pull
|
|
2661
|
+
npm ci # Install exact versions from package-lock.json
|
|
2662
|
+
pm2 restart kilotest
|
|
2663
|
+
```
|
|
2664
|
+
|
|
2665
|
+
This ensures identical dependencies in both environments and prevents conflicts.
|
|
2666
|
+
|
|
2667
|
+
Automate Version Updates in package.json
|
|
2668
|
+
|
|
2669
|
+
You can use npm-check-updates (ncu) to automatically update version numbers in package.json:
|
|
2670
|
+
|
|
2671
|
+
Installation
|
|
2672
|
+
|
|
2673
|
+
```
|
|
2674
|
+
npm install -g npm-check-updates
|
|
2675
|
+
```
|
|
2676
|
+
|
|
2677
|
+
Usage
|
|
2678
|
+
|
|
2679
|
+
```
|
|
2680
|
+
# Check what would be updated
|
|
2681
|
+
ncu
|
|
2682
|
+
|
|
2683
|
+
# Update package.json with latest versions
|
|
2684
|
+
ncu -u
|
|
2685
|
+
|
|
2686
|
+
# Install the new versions
|
|
2687
|
+
npm install
|
|
2688
|
+
|
|
2689
|
+
# Commit and push
|
|
2690
|
+
git add package.json package-lock.json
|
|
2691
|
+
git commit -m "chore: update dependencies"
|
|
2692
|
+
git push
|
|
2693
|
+
```
|
|
2694
|
+
|
|
2695
|
+
Automate with a Script
|
|
2696
|
+
Add to your package.json:
|
|
2697
|
+
|
|
2698
|
+
```
|
|
2699
|
+
{
|
|
2700
|
+
"scripts": {
|
|
2701
|
+
"update-deps": "ncu -u && npm install && git add package.json package-lock.json"
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
```
|
|
2705
|
+
|
|
2706
|
+
Then use:
|
|
2707
|
+
|
|
2708
|
+
```
|
|
2709
|
+
npm run update-deps
|
|
2710
|
+
git commit -m "chore: update dependencies"
|
|
2711
|
+
git push
|
|
2712
|
+
```
|
|
2713
|
+
|
|
2714
|
+
More Control with Target Versions
|
|
2715
|
+
Instead of *, use caret ranges for automatic minor/patch updates:
|
|
2716
|
+
|
|
2717
|
+
```
|
|
2718
|
+
{
|
|
2719
|
+
"dependencies": {
|
|
2720
|
+
"dotenv": "^17.2.3", // allows 17.x.x
|
|
2721
|
+
"testaro": "^2.1.0", // allows 2.x.x
|
|
2722
|
+
"testilo": "^1.5.0" // allows 1.x.x
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
```
|
|
2726
|
+
|
|
2727
|
+
Then npm update will:
|
|
2728
|
+
|
|
2729
|
+
- Update to latest minor/patch versions within the range
|
|
2730
|
+
- Update package-lock.json with exact versions
|
|
2731
|
+
- Not change package.json unless you use ncu -u
|
|
2732
|
+
|
|
2733
|
+
Workflow
|
|
2734
|
+
|
|
2735
|
+
Local development:
|
|
2736
|
+
|
|
2737
|
+
```
|
|
2738
|
+
# When you want to update dependencies
|
|
2739
|
+
ncu -u # Update package.json to latest versions
|
|
2740
|
+
npm install # Install and lock exact versions
|
|
2741
|
+
npm test # Verify everything works
|
|
2742
|
+
git add package.json package-lock.json
|
|
2743
|
+
git commit -m "chore: update dependencies"
|
|
2744
|
+
git push
|
|
2745
|
+
```
|
|
2746
|
+
|
|
2747
|
+
Server deployment:
|
|
2748
|
+
|
|
2749
|
+
```
|
|
2750
|
+
git pull # Gets updated package.json and package-lock.json
|
|
2751
|
+
npm ci # Installs exact locked versions
|
|
2752
|
+
pm2 restart kilotest
|
|
2753
|
+
```
|
|
2754
|
+
|
|
2755
|
+
Alternative: Use npm's Built-in Commands
|
|
2756
|
+
|
|
2757
|
+
For major version updates with interactive prompts:
|
|
2758
|
+
|
|
2759
|
+
```
|
|
2760
|
+
# Check outdated packages
|
|
2761
|
+
npm outdated
|
|
2762
|
+
|
|
2763
|
+
# Update interactively (choose which to update)
|
|
2764
|
+
npx npm-check -u
|
|
2765
|
+
|
|
2766
|
+
# Or update specific packages
|
|
2767
|
+
npm install testaro@latest
|
|
2768
|
+
npm install testilo@latest
|
|
2769
|
+
|
|
2770
|
+
# Commit the changes
|
|
2771
|
+
git add package.json package-lock.json
|
|
2772
|
+
git commit -m "chore: update testaro and testilo"
|
|
2773
|
+
git push
|
|
2774
|
+
```
|
|
2775
|
+
|
|
2776
|
+
Pre-commit Hook (Optional)
|
|
2777
|
+
Ensure package-lock.json is always in sync:
|
|
2778
|
+
|
|
2779
|
+
```
|
|
2780
|
+
# .husky/pre-commit or .git/hooks/pre-commit
|
|
2781
|
+
#!/bin/sh
|
|
2782
|
+
if git diff --cached --name-only | grep -q "package.json"; then
|
|
2783
|
+
if ! git diff --cached --name-only | grep -q "package-lock.json"; then
|
|
2784
|
+
echo "Error: package.json changed but package-lock.json didn't"
|
|
2785
|
+
echo "Run: npm install"
|
|
2786
|
+
exit 1
|
|
2787
|
+
fi
|
|
2788
|
+
fi
|
|
2789
|
+
```
|
|
2790
|
+
|
|
2791
|
+
Recommended Setup
|
|
2792
|
+
1. Change * to ^ ranges in package.json
|
|
2793
|
+
2. Install npm-check-updates globally: npm i -g npm-check-updates
|
|
2794
|
+
3. Add update script to package.json
|
|
2795
|
+
4. Use npm ci on server (never npm update or npm install)
|
|
2796
|
+
|
|
2797
|
+
This gives you:
|
|
2798
|
+
|
|
2799
|
+
✅ Automatic patch/minor updates with npm update
|
|
2800
|
+
✅ Controlled major updates with ncu -u
|
|
2801
|
+
✅ Cross-environment consistency via package-lock.json
|
|
2802
|
+
✅ No git conflicts
|
|
2803
|
+
|
|
2804
|
+
Claude Sonnet 4.5 • 1x
|
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -204,11 +204,20 @@ const goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
204
204
|
}
|
|
205
205
|
// Otherwise, if the response status was rejection of excessive requests:
|
|
206
206
|
else if (httpStatus === 429) {
|
|
207
|
+
const retryHeader = response.headers()['retry-after'];
|
|
208
|
+
let waitSeconds = 5;
|
|
209
|
+
if (retryHeader) {
|
|
210
|
+
waitSeconds = Number.isNaN(Number(retryHeader))
|
|
211
|
+
? Math.ceil((new Date(retryHeader) - new Date()) / 1000)
|
|
212
|
+
: Number(retryHeader);
|
|
213
|
+
}
|
|
207
214
|
// Return this.
|
|
208
|
-
console.log(
|
|
215
|
+
console.log(
|
|
216
|
+
`ERROR: Visit to ${url} rate-limited (status 429); retry after ${waitSeconds} sec.`
|
|
217
|
+
);
|
|
209
218
|
return {
|
|
210
219
|
success: false,
|
|
211
|
-
error:
|
|
220
|
+
error: `status429/retryAfterSeconds=${waitSeconds}`
|
|
212
221
|
};
|
|
213
222
|
}
|
|
214
223
|
// Otherwise, i.e. if the response status was otherwise abnormal:
|
|
@@ -423,11 +432,6 @@ const launch = exports.launch = async (
|
|
|
423
432
|
report.jobData.lastScriptNonce = scriptNonce;
|
|
424
433
|
}
|
|
425
434
|
}
|
|
426
|
-
// Otherwise, i.e. if the navigation was prevented by a request frequency restriction:
|
|
427
|
-
else if (navResult.error === 'status429') {
|
|
428
|
-
// Report this.
|
|
429
|
-
addError(true, false, report, actIndex, 'status429');
|
|
430
|
-
}
|
|
431
435
|
// Otherwise, i.e. if the launch or navigation failed for another reason:
|
|
432
436
|
else {
|
|
433
437
|
// Cause another attempt to launch and navigate, if retries remain.
|
|
@@ -438,8 +442,21 @@ const launch = exports.launch = async (
|
|
|
438
442
|
catch(error) {
|
|
439
443
|
// If retries remain:
|
|
440
444
|
if (retries > 0) {
|
|
441
|
-
|
|
442
|
-
|
|
445
|
+
// Prepare to wait 1 second before a retry.
|
|
446
|
+
let waitSeconds = 1;
|
|
447
|
+
// If the error was a visit failure due to rate limiting:
|
|
448
|
+
if (error.message.includes('status429/retryAfterSeconds=')) {
|
|
449
|
+
// Change the wait to the requested time, if less than 10 seconds.
|
|
450
|
+
const waitSecondsRequest = Number(error.message.replace(/^.+=|\)$/g, ''));
|
|
451
|
+
if (! Number.isNaN(waitSecondsRequest) && waitSecondsRequest < 10) {
|
|
452
|
+
waitSeconds = waitSecondsRequest;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
console.log(
|
|
456
|
+
`WARNING: Waiting ${waitSeconds} sec. before retrying (retries left: ${retries})`
|
|
457
|
+
);
|
|
458
|
+
await wait(1000 * waitSeconds + 100);
|
|
459
|
+
// Then retry the launch and navigation.
|
|
443
460
|
return launch(report, debug, waits, tempBrowserID, tempURL, retries - 1);
|
|
444
461
|
}
|
|
445
462
|
// Otherwise, i.e. if no retries remain:
|
|
@@ -804,6 +821,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
804
821
|
if (act.data && ! act.data.prevented) {
|
|
805
822
|
// If standardization is required:
|
|
806
823
|
if (['also', 'only'].includes(standard)) {
|
|
824
|
+
console.log('>>>>>> Standardizing');
|
|
807
825
|
// Initialize the standard result.
|
|
808
826
|
act.standardResult = {
|
|
809
827
|
totals: [0, 0, 0, 0],
|