testaro 74.2.0 → 74.2.2
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 +348 -0
- package/package.json +1 -1
- package/run.js +7 -8
package/UPGRADES.md
CHANGED
|
@@ -8360,3 +8360,351 @@ pool@jpdev testaro % claude
|
|
|
8360
8360
|
|
|
8361
8361
|
claude --resume bb937c34-19d9-4fd5-b04b-1356fb39b355
|
|
8362
8362
|
```
|
|
8363
|
+
|
|
8364
|
+
## Initial modal dialogs
|
|
8365
|
+
|
|
8366
|
+
|
|
8367
|
+
|
|
8368
|
+
|
|
8369
|
+
|
|
8370
|
+
|
|
8371
|
+
|
|
8372
|
+
|
|
8373
|
+
Playwright provides several methods to detect modal dialogs, ranging from native browser dialogs to custom modal components.
|
|
8374
|
+
|
|
8375
|
+
## Modal Detection Methods
|
|
8376
|
+
|
|
8377
|
+
### 1. Native Browser Dialogs (alert, confirm, prompt)
|
|
8378
|
+
```javascript
|
|
8379
|
+
// Handle native dialogs that block execution
|
|
8380
|
+
page.on('dialog', async dialog => {
|
|
8381
|
+
console.log('Dialog type:', dialog.type());
|
|
8382
|
+
console.log('Dialog message:', dialog.message());
|
|
8383
|
+
await dialog.accept(); // or dialog.dismiss()
|
|
8384
|
+
});
|
|
8385
|
+
|
|
8386
|
+
// Check if dialog handler was triggered
|
|
8387
|
+
let dialogAppeared = false;
|
|
8388
|
+
page.on('dialog', () => { dialogAppeared = true; });
|
|
8389
|
+
await someAction(); // Action that might trigger dialog
|
|
8390
|
+
if (dialogAppeared) {
|
|
8391
|
+
// Handle modal
|
|
8392
|
+
}
|
|
8393
|
+
```
|
|
8394
|
+
|
|
8395
|
+
### 2. ARIA-Based Modal Detection
|
|
8396
|
+
```javascript
|
|
8397
|
+
// Detect modals by ARIA role and attributes
|
|
8398
|
+
async function detectAriaModal(page) {
|
|
8399
|
+
const modalSelectors = [
|
|
8400
|
+
'[role="dialog"]',
|
|
8401
|
+
'[role="alertdialog"]',
|
|
8402
|
+
'[aria-modal="true"]'
|
|
8403
|
+
];
|
|
8404
|
+
|
|
8405
|
+
for (const selector of modalSelectors) {
|
|
8406
|
+
const modal = page.locator(selector);
|
|
8407
|
+
if (await modal.isVisible()) {
|
|
8408
|
+
return modal;
|
|
8409
|
+
}
|
|
8410
|
+
}
|
|
8411
|
+
return null;
|
|
8412
|
+
}
|
|
8413
|
+
|
|
8414
|
+
// Usage
|
|
8415
|
+
const modal = await detectAriaModal(page);
|
|
8416
|
+
if (modal) {
|
|
8417
|
+
console.log('ARIA modal detected');
|
|
8418
|
+
// Test modal accessibility
|
|
8419
|
+
}
|
|
8420
|
+
```
|
|
8421
|
+
|
|
8422
|
+
### 3. CSS-Based Modal Detection
|
|
8423
|
+
```javascript
|
|
8424
|
+
// Detect modals by common CSS patterns
|
|
8425
|
+
async function detectCssModal(page) {
|
|
8426
|
+
const modalSelectors = [
|
|
8427
|
+
'.modal:visible',
|
|
8428
|
+
'.modal-dialog:visible',
|
|
8429
|
+
'.modal-overlay:visible',
|
|
8430
|
+
'.popup:visible',
|
|
8431
|
+
'.dialog:visible',
|
|
8432
|
+
'[style*="display: block"][style*="position: fixed"]'
|
|
8433
|
+
];
|
|
8434
|
+
|
|
8435
|
+
for (const selector of modalSelectors) {
|
|
8436
|
+
try {
|
|
8437
|
+
const modal = page.locator(selector);
|
|
8438
|
+
if (await modal.isVisible({ timeout: 1000 })) {
|
|
8439
|
+
return modal;
|
|
8440
|
+
}
|
|
8441
|
+
} catch (e) {
|
|
8442
|
+
// Element not found, continue
|
|
8443
|
+
}
|
|
8444
|
+
}
|
|
8445
|
+
return null;
|
|
8446
|
+
}
|
|
8447
|
+
```
|
|
8448
|
+
|
|
8449
|
+
### 4. Comprehensive Modal Detection
|
|
8450
|
+
```javascript
|
|
8451
|
+
async function detectAnyModal(page) {
|
|
8452
|
+
// Check native dialogs first
|
|
8453
|
+
let dialogAppeared = false;
|
|
8454
|
+
const dialogHandler = () => { dialogAppeared = true; };
|
|
8455
|
+
page.on('dialog', dialogHandler);
|
|
8456
|
+
|
|
8457
|
+
// Check ARIA modals
|
|
8458
|
+
const ariaModal = await detectAriaModal(page);
|
|
8459
|
+
if (ariaModal) return { type: 'aria', element: ariaModal };
|
|
8460
|
+
|
|
8461
|
+
// Check CSS modals
|
|
8462
|
+
const cssModal = await detectCssModal(page);
|
|
8463
|
+
if (cssModal) return { type: 'css', element: cssModal };
|
|
8464
|
+
|
|
8465
|
+
// Check native dialog
|
|
8466
|
+
if (dialogAppeared) return { type: 'native', element: null };
|
|
8467
|
+
|
|
8468
|
+
return null;
|
|
8469
|
+
}
|
|
8470
|
+
```
|
|
8471
|
+
|
|
8472
|
+
### 5. Accessibility-Focused Modal Testing
|
|
8473
|
+
```javascript
|
|
8474
|
+
async function testModalAccessibility(page) {
|
|
8475
|
+
const modal = await detectAnyModal(page);
|
|
8476
|
+
if (!modal) return null;
|
|
8477
|
+
|
|
8478
|
+
const element = modal.element;
|
|
8479
|
+
const accessibilityTests = {
|
|
8480
|
+
hasAccessibleName: await element.getAttribute('aria-label') ||
|
|
8481
|
+
await element.getAttribute('aria-labelledby'),
|
|
8482
|
+
hasModalRole: await element.getAttribute('role') === 'dialog' ||
|
|
8483
|
+
await element.getAttribute('aria-modal') === 'true',
|
|
8484
|
+
isFocusTrapped: await testFocusTrap(page, element),
|
|
8485
|
+
hasCloseMethod: await page.locator('[aria-label="Close"], .close, button[title="Close"]').isVisible()
|
|
8486
|
+
};
|
|
8487
|
+
|
|
8488
|
+
return {
|
|
8489
|
+
detected: modal,
|
|
8490
|
+
accessibility: accessibilityTests
|
|
8491
|
+
};
|
|
8492
|
+
}
|
|
8493
|
+
|
|
8494
|
+
async function testFocusTrap(page, modalElement) {
|
|
8495
|
+
// Test Tab key stays within modal
|
|
8496
|
+
await page.keyboard.press('Tab');
|
|
8497
|
+
const focusedElement = await page.locator(':focus');
|
|
8498
|
+
const modalContainsFocus = await modalElement.contains(focusedElement);
|
|
8499
|
+
return modalContainsFocus;
|
|
8500
|
+
}
|
|
8501
|
+
```
|
|
8502
|
+
|
|
8503
|
+
## Implementation for Testaro
|
|
8504
|
+
|
|
8505
|
+
```javascript
|
|
8506
|
+
// Modal detection workflow for accessibility testing
|
|
8507
|
+
async function checkPageModals(page) {
|
|
8508
|
+
const modalInfo = await testModalAccessibility(page);
|
|
8509
|
+
|
|
8510
|
+
if (modalInfo) {
|
|
8511
|
+
console.log(`Modal detected: ${modalInfo.detected.type}`);
|
|
8512
|
+
|
|
8513
|
+
// Test focus management
|
|
8514
|
+
await testFocusManagement(page, modalInfo.detected.element);
|
|
8515
|
+
|
|
8516
|
+
// Test keyboard dismissability
|
|
8517
|
+
await testKeyboardDismiss(page, modalInfo.detected.element);
|
|
8518
|
+
|
|
8519
|
+
return modalInfo;
|
|
8520
|
+
}
|
|
8521
|
+
|
|
8522
|
+
return null;
|
|
8523
|
+
}
|
|
8524
|
+
|
|
8525
|
+
async function testFocusManagement(page, modal) {
|
|
8526
|
+
// Check if focus is properly managed
|
|
8527
|
+
const initialFocus = await page.locator(':focus');
|
|
8528
|
+
await page.keyboard.press('Tab');
|
|
8529
|
+
const afterTabFocus = await page.locator(':focus');
|
|
8530
|
+
|
|
8531
|
+
return {
|
|
8532
|
+
focusInitiallyInModal: await modal.contains(initialFocus),
|
|
8533
|
+
focusRemainsInModal: await modal.contains(afterTabFocus)
|
|
8534
|
+
};
|
|
8535
|
+
}
|
|
8536
|
+
```
|
|
8537
|
+
|
|
8538
|
+
This comprehensive approach allows Testaro to detect and test accessibility of various modal types across different implementations.
|
|
8539
|
+
|
|
8540
|
+
## State cleaning before each page load
|
|
8541
|
+
|
|
8542
|
+
|
|
8543
|
+
|
|
8544
|
+
|
|
8545
|
+
|
|
8546
|
+
|
|
8547
|
+
|
|
8548
|
+
|
|
8549
|
+
|
|
8550
|
+
|
|
8551
|
+
Yes, Playwright provides comprehensive state reset capabilities that are consistent across Chromium, WebKit, and Firefox.
|
|
8552
|
+
|
|
8553
|
+
## Complete State Reset Capabilities
|
|
8554
|
+
|
|
8555
|
+
### Available Methods
|
|
8556
|
+
Playwright's `BrowserContext` class provides these state management methods:
|
|
8557
|
+
|
|
8558
|
+
**Cookies:**
|
|
8559
|
+
```javascript
|
|
8560
|
+
await context.clearCookies(); // Clear all cookies
|
|
8561
|
+
await context.clearCookies({ domain: 'example.com' }); // Clear specific domain
|
|
8562
|
+
```
|
|
8563
|
+
|
|
8564
|
+
**Storage State:**
|
|
8565
|
+
```javascript
|
|
8566
|
+
// Clear and reset all storage
|
|
8567
|
+
await context.setStorageState({ cookies: [], origins: [] });
|
|
8568
|
+
|
|
8569
|
+
// Save current state
|
|
8570
|
+
await context.storageState({ path: 'state.json' });
|
|
8571
|
+
|
|
8572
|
+
// Load clean state (effectively a reset)
|
|
8573
|
+
await context.setStorageState('clean-state.json');
|
|
8574
|
+
```
|
|
8575
|
+
|
|
8576
|
+
### Complete Reset Implementation
|
|
8577
|
+
For the "Complete State Reset" I listed earlier:
|
|
8578
|
+
|
|
8579
|
+
```javascript
|
|
8580
|
+
// Universal state reset across all browsers
|
|
8581
|
+
async function resetBrowserState(context) {
|
|
8582
|
+
await context.clearCookies(); // Clear all cookies
|
|
8583
|
+
await context.clearPermissions(); // Clear permission overrides
|
|
8584
|
+
await context.setStorageState({ // Clear localStorage, sessionStorage, IndexedDB
|
|
8585
|
+
cookies: [],
|
|
8586
|
+
origins: []
|
|
8587
|
+
});
|
|
8588
|
+
|
|
8589
|
+
// Alternative: Create fresh context
|
|
8590
|
+
const freshContext = await browser.newContext();
|
|
8591
|
+
return freshContext;
|
|
8592
|
+
}
|
|
8593
|
+
```
|
|
8594
|
+
|
|
8595
|
+
## Cross-Browser Compatibility
|
|
8596
|
+
|
|
8597
|
+
**Yes, the methods are identical across all three browsers:**
|
|
8598
|
+
|
|
8599
|
+
- **Chromium**: Full support for all state management APIs
|
|
8600
|
+
- **WebKit**: Full support for all state management APIs
|
|
8601
|
+
- **Firefox**: Full support for all state management APIs
|
|
8602
|
+
|
|
8603
|
+
### Key Points:
|
|
8604
|
+
|
|
8605
|
+
1. **API Consistency**: The same `BrowserContext` methods work identically across all browsers
|
|
8606
|
+
2. **Storage Coverage**: Methods handle cookies, localStorage, sessionStorage, and IndexedDB uniformly
|
|
8607
|
+
3. **No Browser-Specific Code**: No conditional logic needed for different browsers
|
|
8608
|
+
|
|
8609
|
+
### Implementation for Testaro
|
|
8610
|
+
|
|
8611
|
+
```javascript
|
|
8612
|
+
// Browser-agnostic state reset
|
|
8613
|
+
async function ensureCleanState(browser, testUrl) {
|
|
8614
|
+
const context = await browser.newContext();
|
|
8615
|
+
await context.clearCookies();
|
|
8616
|
+
await context.setStorageState({ cookies: [], origins: [] });
|
|
8617
|
+
|
|
8618
|
+
const page = await context.newPage();
|
|
8619
|
+
await page.goto(testUrl);
|
|
8620
|
+
|
|
8621
|
+
return { context, page };
|
|
8622
|
+
}
|
|
8623
|
+
```
|
|
8624
|
+
|
|
8625
|
+
## Multi-State Testing Strategy
|
|
8626
|
+
|
|
8627
|
+
With Playwright's consistent APIs, Testaro can implement:
|
|
8628
|
+
|
|
8629
|
+
1. **First Load Test**: Fresh context → no cookies → test initial page
|
|
8630
|
+
2. **Dialog State Test**: Interact with dialogs → retest
|
|
8631
|
+
3. **Post-Interaction Test**: Dismiss dialogs → retest clean state
|
|
8632
|
+
|
|
8633
|
+
All using the same codebase across Chromium, WebKit, and Firefox.
|
|
8634
|
+
|
|
8635
|
+
## Code-hygiene-based testing
|
|
8636
|
+
|
|
8637
|
+
|
|
8638
|
+
Your investigation reveals critical flaws in current accessibility testing methodology that go far beyond the initial SVG issue.
|
|
8639
|
+
|
|
8640
|
+
## (1) Test Isolation and State Management Problems
|
|
8641
|
+
|
|
8642
|
+
### The Cookie State Issue
|
|
8643
|
+
Your discovery demonstrates that **page state fundamentally affects accessibility testing**:
|
|
8644
|
+
|
|
8645
|
+
- **First load (no cookies)**: Cookie banner appears, SVG becomes visible without accessible name
|
|
8646
|
+
- **Subsequent loads (with cookies)**: Cookie banner suppressed, SVG remains hidden
|
|
8647
|
+
- **Current testing**: May capture only one state, missing violations in the other
|
|
8648
|
+
|
|
8649
|
+
### Required Testaro Improvements
|
|
8650
|
+
|
|
8651
|
+
**Complete State Reset**:
|
|
8652
|
+
- Clear all site cookies before each page load
|
|
8653
|
+
- Clear localStorage, sessionStorage, IndexedDB
|
|
8654
|
+
- Reset browser state to truly "first visit" conditions
|
|
8655
|
+
|
|
8656
|
+
**Multi-State Testing**:
|
|
8657
|
+
1. **First Load Test**: Test page with no stored state
|
|
8658
|
+
2. **Dialog Dismissal Test**: Dismiss initial dialogs, then retest
|
|
8659
|
+
3. **State Change Test**: Test after common user interactions
|
|
8660
|
+
|
|
8661
|
+
**Modal Dialog Testing**:
|
|
8662
|
+
- Identify modal dialogs on first load
|
|
8663
|
+
- Test focus trapping behavior
|
|
8664
|
+
- Verify keyboard dismissability
|
|
8665
|
+
- Check ARIA attributes in visible state
|
|
8666
|
+
|
|
8667
|
+
## (2) Rule Definition Philosophy: Momentary vs. Hygiene
|
|
8668
|
+
|
|
8669
|
+
### Current Problem: Momentary Correctness
|
|
8670
|
+
Rules that test "is this accessible right now?" fail because:
|
|
8671
|
+
- They miss conditional violations
|
|
8672
|
+
- They don't account for state changes
|
|
8673
|
+
- They create false negatives/positives
|
|
8674
|
+
|
|
8675
|
+
### Better Approach: Coding Hygiene Rules
|
|
8676
|
+
|
|
8677
|
+
Your SVG example illustrates this perfectly:
|
|
8678
|
+
**Poor hygiene**: SVG lacks accessible name, relies on ancestor being hidden
|
|
8679
|
+
**Good hygiene**: SVG has accessible name regardless of visibility state
|
|
8680
|
+
|
|
8681
|
+
### Hygiene-Based Rule Examples
|
|
8682
|
+
|
|
8683
|
+
1. **SVG Naming Rule**: All SVG elements must have accessible names, regardless of ancestor visibility
|
|
8684
|
+
2. **State Independence Rule**: Element accessibility must not depend on CSS display properties
|
|
8685
|
+
3. **Consistency Rule**: If an element can become visible, it must be accessible in all states
|
|
8686
|
+
|
|
8687
|
+
## Benefits of Hygiene-Based Rules
|
|
8688
|
+
|
|
8689
|
+
1. **Prevention-focused**: Catch issues before they cause accessibility problems
|
|
8690
|
+
2. **State-agnostic**: Don't depend on specific page states during testing
|
|
8691
|
+
3. **Developer-friendly**: Clear coding standards rather than conditional requirements
|
|
8692
|
+
4. **Reduced false positives**: Rules apply consistently regardless of testing conditions
|
|
8693
|
+
|
|
8694
|
+
## Implementation Strategy
|
|
8695
|
+
|
|
8696
|
+
**Testaro Enhancement**:
|
|
8697
|
+
- Add state isolation procedures
|
|
8698
|
+
- Implement multi-state testing workflows
|
|
8699
|
+
- Develop hygiene-focused rule sets
|
|
8700
|
+
- Create modal dialog detection and testing
|
|
8701
|
+
|
|
8702
|
+
**AI Agent Evolution**:
|
|
8703
|
+
- Recognize state-dependent patterns
|
|
8704
|
+
- Test elements across multiple states
|
|
8705
|
+
- Apply hygiene-based rule evaluation
|
|
8706
|
+
- Predict potential accessibility failures
|
|
8707
|
+
|
|
8708
|
+
## Conclusion
|
|
8709
|
+
|
|
8710
|
+
Your investigation reveals that **current accessibility testing is fundamentally incomplete**. The solution requires both technical improvements (state management) and philosophical shifts (hygiene-based rules). This significantly raises the complexity bar but also the potential effectiveness of automated accessibility testing.
|
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -21,16 +21,15 @@ require('dotenv').config({quiet: true});
|
|
|
21
21
|
const {isValidJob} = require('./procs/job');
|
|
22
22
|
const {getCatalog} = require('./procs/catalog');
|
|
23
23
|
const {nowString} = require('./procs/dateTime');
|
|
24
|
-
|
|
25
|
-
const {chromium} = require('playwright-extra');
|
|
24
|
+
const {chromium, webkit, firefox} = require('playwright-extra');
|
|
26
25
|
const fs = require('fs').promises;
|
|
27
26
|
const os = require('os');
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
/*
|
|
28
|
+
Module to evade detection of Chromium automation. Injects launch flags
|
|
29
|
+
(e.g., `--disable-blink-features=AutomationControlled`) and patches
|
|
30
|
+
Blink-only DOM globals. Job-level opt-out for Chromium happens in
|
|
31
|
+
procs/launch.js via the `stealth` field.
|
|
32
|
+
*/
|
|
34
33
|
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
|
35
34
|
chromium.use(StealthPlugin());
|
|
36
35
|
|