In 2022, I started the move to Mastodon on the Activity Pub platform. As I started in the community, people would advocate for including accessibility on the platform. In a good way, it was drummed into my head that every image posted should have ALT text added to the images to improve accessibility, especially for folks using screen readers. That’s something I'm happy to support, especially as my own eyes move to the reading glasses stage. I personally take issue with photographs of page-size documents where it’s very difficult to read the text zooming around an iPhone window.
I'm now most active on PixelFed at https://pixey.org/@iolaire. I enjoy posting photos and scrolling through everyone else's photos. However, I often am lazy or don't want to type the alt text on the phone. Luckily, Altbot would reply to my post with AI-generated captions. However, at some point, they implemented GDPR approval, and it no longer worked with PixelFed.
So this year, I started on a small project to backfill missing alt text on my PixelFed account. I wrote a nice small Flask script to generate image descriptions using llava:7b running on the Ollama server locally. When I say wrote, I started out with AI prompts in the various AI chat programs and then moved to using Amazon Q to polish it up.
However, on July 14th, Kiro from Amazon dropped providing a framework to do spec-driven development. As a getting stuff done person I had to try Kiro to practice a more organized and professional development style, and my 2,500-line Python script started growing quickly, its now over 200k lines:
√ Implement multi-user processing support
√ Enhance error handling and recovery
√ Optimize image processing
√ Optimize Ollama integration with llava:7b model
√ Improve database management
√ Enhance web interface
√ Improve ActivityPub integration
√ Implement system monitoring and management
√ Add comprehensive testing
√ Implement Multi-Platform ActivityPub Support
... and the list goes on
At some point, my specs included csrf-security-audit
(don't add this mid-development, do it near the end), and I began frequent debugging using the JavaScript console. I've been learning a lot about CSRF tokens.
Pasting over JavaScript console errors into the chat for debugging became quite cumbersome. The Python tests Kiro wrote for me didn't actually catch frontend errors like CSRF token missmatches. So I went down the productive path of asking Kiro to write Playwright tests.
You can start to make progress on those CSRF token errors with commands like test the admin dashboard with playwright, use the admin user admin/abc134, look for JavaScript console errors, and resolve the issues
.
Sometimes, to test redirects on pages, it’s great to let Kiro use the Playwright MCP server and step through the process.
Installing the MCP server is fairly simple once it’s on your system. In my case on a Mac Mini M4, it’s:
{
"mcpServers": {
"playwright": {
"command": "npx",
"args": [
"@playwright/mcp@latest"
],
"disabled": false,
"autoApprove": [
"browser_click",
"browser_wait_for",
"browser_navigate"
]
}
}
}
Note that a few steps are auto-approved; for most others, I like to run Playwright in headless mode = false and watch the test in case I need to give feedback to Kiro.
When running the tests via script, I've learned a few things, and Kiro helped to create a robust (maybe too robust) steering document - playwright-testing-guidelines.md. That steering document is at the end of the post.
Now, many spec tasks will do some visual testing with Playwright, but when it’s time to debug something, I'll often instruct Kiro to use the Playwright MCP to visually walk through the bug and review both the JavaScript console output and the app logs to identify and resolve the bug.
playwright-testing-guidelines.md steering document content:
Playwright Testing Guidelines
Mandatory Requirements
Timeouts (REQUIRED)
# Always prefix with timeout (run from tests/playwright/)
timeout 120 npx playwright test --config=0830_17_52_playwright.config.js
Configuration Timeouts
module.exports = defineConfig({
timeout: 120000, // 120 seconds
expect: { timeout: 30000 }, // 30 seconds
use: {
headless: false, // MANDATORY for debugging
actionTimeout: 30000,
navigationTimeout: 60000
},
webServer: { timeout: 120000 }
});
Authentication (MANDATORY)
const { ensureLoggedOut } = require('../utils/0830_17_52_auth_utils');
test.describe('Test Suite', () => {
test.beforeEach(async ({ page }) => {
await ensureLoggedOut(page); // MANDATORY cleanup
});
test.afterEach(async ({ page }) => {
await ensureLoggedOut(page); // MANDATORY cleanup
});
});
File Organization
Directory Structure
tests/playwright/
├── tests/ # All test files
├── utils/ # Utility functions
├── 0830_17_52_playwright.config.js # Configuration
├── 0830_17_52_package.json # Dependencies
└── 0830_17_52_README.md # Documentation
File Naming (MANDATORY)
All files MUST use timestamp prefix: MMdd_HH_mm_filename.js
Browser Configuration
-
Headless:
false
(MANDATORY for debugging) - Primary: WebKit (Safari)
- Secondary: Chromium, Firefox
Command Examples
# Navigate to correct directory first
cd tests/playwright
# Run tests
timeout 120 npx playwright test --config=0830_17_52_playwright.config.js
# Debug mode
timeout 120 npx playwright test --config=0830_17_52_playwright.config.js --debug
# Specific test
timeout 120 npx playwright test tests/0830_17_52_test_admin.js --timeout=120
Landing Page Tests (NEW)
Running Landing Page Tests
# Navigate to correct directory first
cd tests/playwright
# Start web application first
python web_app.py & sleep 10
# Run accessibility tests (REQUIRED timeout prefix)
timeout 120 npx playwright test tests/0905_14_50_test_landing_page_accessibility.js --config=0830_17_52_playwright.config.js
# Run UI tests
timeout 120 npx playwright test tests/0905_14_50_test_landing_page_ui.js --config=0830_17_52_playwright.config.js
# Debug mode
timeout 120 npx playwright test tests/0905_14_50_test_landing_page_accessibility.js --config=0830_17_52_playwright.config.js --debug
# Use convenience script
./0905_14_50_run_landing_page_tests.sh --accessibility
./0905_14_50_run_landing_page_tests.sh --ui
Critical Security Issues
Page.evaluate() Security Error
Problem: SecurityError: The operation is insecure
in WebKit
Solution: Avoid page.evaluate()
for storage cleanup
// CORRECT - Safe cleanup
test.beforeEach(async ({ page }) => {
await page.context().clearCookies();
});
// WRONG - Causes SecurityError
test.beforeEach(async ({ page }) => {
await page.evaluate(() => {
localStorage.clear(); // SecurityError!
});
});
Navigation Timeout Prevention
Problem: networkidle
timeouts with WebSockets
Solution: Use domcontentloaded
// CORRECT
await page.goto('/login', {
waitUntil: 'domcontentloaded',
timeout: 30000
});
// WRONG - Times out
await page.goto('/login', {
waitUntil: 'networkidle'
});
Quality Standards
- All tests must pass consistently
- No console errors (WebSocket, CORS, notifications)
- Proper cleanup after each test
- Clear, descriptive test names
Generic Test Running Instructions (For Any Playwright Test)
Standard Test Execution Pattern
# 1. ALWAYS navigate to correct directory first
cd tests/playwright
# 2. Start web application if needed (most tests require this)
python web_app.py & sleep 10
# 3. Verify app is running
curl -s http://127.0.0.1:5000 | head -5
# 4. Run test with MANDATORY timeout prefix and working config
timeout 120 npx playwright test tests/[TIMESTAMP]_test_[NAME].js --config=0830_17_52_playwright.config.js
# 5. For debugging, add --debug flag
timeout 120 npx playwright test tests/[TIMESTAMP]_test_[NAME].js --config=0830_17_52_playwright.config.js --debug
Test File Pattern Recognition
- All test files follow:
tests/MMdd_HH_mm_test_[description].js
- Always use existing working config:
0830_17_52_playwright.config.js
- Always use timeout prefix:
timeout 120
- Always run from
tests/playwright/
directory
Universal Prerequisites Checklist
- ✅ Navigate to
tests/playwright/
directory - ✅ Start web application:
python web_app.py & sleep 10
- ✅ Verify app responds:
curl http://127.0.0.1:5000
- ✅ Use timeout prefix:
timeout 120
- ✅ Use working config:
--config=0830_17_52_playwright.config.js
- ✅ For debugging: add
--debug
flag
Common Error Prevention
- "No tests found": Check file path and use correct config
- Timeout errors: Ensure web app is running first
-
Navigation errors: Use
domcontentloaded
notnetworkidle
-
Security errors: Avoid
page.evaluate()
for storage operations -
Browser errors: Ensure browsers installed:
npx playwright install
Troubleshooting
- Verify web app running on
http://127.0.0.1:5000
- Confirm admin/user accounts exist
- Check browser console for errors
- Use debug mode for step-by-step execution
AJAX Form Testing (Lessons Learned)
JavaScript Event Handling
Problem: JavaScript event listeners may not be properly attached or executed in Playwright.
Solution: Use direct element interaction and verify JavaScript execution:
// CORRECT - Direct button click with proper waiting
await page.click('button[type="submit"]');
await page.wait_for_timeout(5000); // Wait for AJAX completion
// ALSO CORRECT - Verify JavaScript is executing
const js_event_listener = await page.evaluate('''
() => {
const form = document.querySelector('#edit-mode form');
if (form) {
return form.onsubmit !== null;
}
return false;
}
''');
console.log(`JavaScript event listener attached: ${js_event_listener}`);
Console Message Monitoring
Problem: JavaScript errors and AJAX responses may not be visible in test output.
Solution: Set up real-time console message capture:
// CORRECT - Real-time console monitoring
console_messages = [];
def console_handler(msg):
message = f"Console {msg.type}: {msg.text}"
console_messages.append(message);
print(f" {message}");
page.on("console", console_handler);
AJAX Form Submission Testing
Problem: Forms using AJAX may not trigger traditional form submission events.
Solution: Test both direct interaction and verify AJAX responses:
// CORRECT - Test AJAX form submission
await page.fill('#first_name', 'TestValue');
await page.click('button[type="submit"]');
// Wait for AJAX completion
await page.wait_for_timeout(5000);
// Verify success through console logs
if any('Response data: {success: true}' in msg for msg in console_messages):
console.log("✅ AJAX submission successful");
Page Reload Handling
Problem: AJAX forms that reload pages may cause timeout issues.
Solution: Use appropriate timeouts and verify page state:
// CORRECT - Handle page reloads after AJAX
await page.wait_for_timeout(5000); // Wait for reload
await page.wait_for_selector('#view-mode', state='visible', timeout=30000);
// Alternative - Check URL if page reloads
current_url = page.url;
if current_url.includes('/profile'):
console.log("✅ Page reload successful");
Form Validation Testing
Problem: CSRF protection and form validation may fail silently.
Solution: Verify form data and CSRF tokens:
// CORRECT - Check form structure and CSRF
form_html = await page.inner_html('#edit-mode form');
console.log(`Form contains CSRF token: ${'csrf_token' in form_html}`);
// Verify form data submission
console.log("Form data being submitted:");
for (let [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
Browser-Specific Behavior
Problem: Different browsers may handle JavaScript and AJAX differently.
Solution: Test across browsers and use appropriate waits:
// CORRECT - Browser-agnostic testing
const browsers = ['webkit', 'chromium', 'firefox'];
for (const browser of browsers) {
const context = await browser.newContext();
const page = await context.newPage();
// Test implementation
await browser.close();
}
Debug Mode Best Practices
Problem: Headless mode may hide JavaScript and AJAX issues.
Solution: Always use headless mode for debugging:
// CORRECT - Debug configuration
const browser = await p.webkit.launch(headless=false); // MANDATORY
const context = await browser.newContext();
const page = await context.newPage();
// Add screenshots for debugging
await page.screenshot(path='debug_result.png');
Test Organization for AJAX Features
Problem: AJAX functionality requires comprehensive testing of both client and server sides.
Solution: Create dedicated test files for AJAX features:
tests/playwright/
├── test_profile_editing_functionality.py # AJAX form tests
├── debug_profile_submission.py # Debug scripts
└── utils/
└── ajax_helpers.py # AJAX test utilities
Top comments (1)
When you're testing with Playwright, you’ll run into some classic JavaScript console errors that can trip you up. Stuff like network errors, type errors from trying to use something that ain’t defined, CORS drama when you're crossing domains without permission, and those lovely uncaught exceptions when your DOM elements ghost you. Now, if you're rolling with the Playwright MCP server, you’re in luck - it cranks up the efficiency by letting you run tests in parallel across multiple setups, cuts down that wait time, and gives you a central log hub to untangle bugs without losing your mind. CSRF issues can be sneaky here, so keep an eye on those network requests and double-check how your tokens are being handled in the session.