Playwright is a modern, open-source testing framework from Microsoft that enables reliable end-to-end testing for web applications. It supports multiple browsers and provides powerful automation capabilities essential for validating security controls in Cursor deployments.
Built by creators of Puppeteer (Google Chrome team)
Core Capabilities
1. Cross-Browser Testing
Browser Support:
Chromium (Chrome, Edge)
Firefox
WebKit (Safari)
Mobile emulation
2. Auto-Wait
Reliability Feature:
1
2
3
4
5
// Playwright automatically waits for elementsawaitpage.click('button#login');// Waits for button to be clickableawaitpage.fill('#api-key',apiKey);// Waits for input to be enabled// No manual waits needed (vs Selenium)
// Test authentication for Cursor web portalimport{test,expect}from'@playwright/test';test('Okta SSO login enforces MFA',async({page})=>{// 1. Navigate to Cursor portalawaitpage.goto('https://cursor.company.com');// 2. Click SSO loginawaitpage.click('button:has-text("Sign in with SSO")');// 3. Enter company domainawaitpage.fill('#domain','company.com');awaitpage.click('#next');// 4. Redirected to Oktaawaitexpect(page).toHaveURL(/.*okta\.com.*/);// 5. Enter credentialsawaitpage.fill('#username',process.env.TEST_USER);awaitpage.fill('#password',process.env.TEST_PASSWORD);awaitpage.click('#submit');// 6. MFA challenge should appearawaitexpect(page.locator('text=Verify with Okta')).toBeVisible();// 7. Complete MFA (using test account)awaitpage.click('#push-notification');// Wait for approval (in test, auto-approve)awaitpage.waitForURL('https://cursor.company.com/dashboard');// 8. Verify logged inawaitexpect(page.locator('text=Welcome')).toBeVisible();});test('Cannot bypass Okta MFA',async({page})=>{// Attempt to access dashboard directlyawaitpage.goto('https://cursor.company.com/dashboard');// Should redirect to loginawaitexpect(page).toHaveURL(/.*okta\.com.*/);// Even with valid session cookie manipulationawaitpage.context().addCookies([{name:'session',value:'fake-session-token',domain:'cursor.company.com',path:'/'}]);awaitpage.goto('https://cursor.company.com/dashboard');// Should still redirect (MFA required)awaitexpect(page).toHaveURL(/.*okta\.com.*/);});
Test DLP Blocking:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
test('Purview DLP blocks API key in Teams',async({page})=>{awaitpage.goto('https://teams.microsoft.com');// Login...// Try to send Azure OpenAI key in chatconstfakeApiKey='sk-proj-test1234567890abcdef';awaitpage.fill('#message-input',`Here's the API key: ${fakeApiKey}`);awaitpage.click('#send-button');// Should see DLP block messageawaitexpect(page.locator('text=blocked by policy')).toBeVisible({timeout:5000});// Message should not be sentconstmessages=awaitpage.locator('.message-content').allTextContents();expect(messages).not.toContain(fakeApiKey);});
test('Azure Firewall blocks unauthorized domains',async({page})=>{// Configure Playwright to use enterprise proxyconstbrowser=awaitchromium.launch({proxy:{server:'http://azure-firewall.company.com:8080'}});constcontext=awaitbrowser.newContext();constpage=awaitcontext.newPage();// Attempt to access blocked domaintry{awaitpage.goto('https://unauthorized-llm-provider.com',{timeout:5000});// Should not reach herethrownewError('Expected navigation to be blocked');}catch(error){// Verify it's a firewall block, not network errorconstcontent=awaitpage.content();expect(content).toContain('Azure Firewall');expect(content).toContain('blocked by security policy');}awaitbrowser.close();});
test('Developer role cannot access production secrets',async({page})=>{// Login as developerawaitloginAsUser(page,'developer@company.com');// Navigate to Key Vaultawaitpage.goto('https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.KeyVault%2Fvaults');// Try to access production Key Vaultawaitpage.click('text=keyvault-prod-001');// Try to view secretsawaitpage.click('text=Secrets');// Should see access deniedawaitexpect(page.locator('text=Access denied')).toBeVisible();awaitexpect(page.locator('text=You do not have permission')).toBeVisible();});test('Security admin can access production secrets',async({page})=>{// Login as security adminawaitloginAsUser(page,'secadmin@company.com');// Navigate to Key Vaultawaitpage.goto('https://portal.azure.com/#view/HubsExtension/BrowseResource/resourceType/Microsoft.KeyVault%2Fvaults');// Access production Key Vaultawaitpage.click('text=keyvault-prod-001');// View secretsawaitpage.click('text=Secrets');// Should see secret listawaitexpect(page.locator('.secret-list')).toBeVisible();// Audit: Record accessconstsecretNames=awaitpage.locator('.secret-name').allTextContents();console.log(`Security admin accessed ${secretNames.length} secrets`);});
name:Security Testson:push:branches:[main,develop]pull_request:branches:[main]schedule:# Run daily at 2 AM UTC-cron:'02***'jobs:security-tests:runs-on:ubuntu-lateststeps:-uses:actions/checkout@v3-uses:actions/setup-node@v3with:node-version:18-name:Install dependenciesrun:|npm cinpx playwright install --with-deps-name:Run security testsrun:npx playwright test --grep @securityenv:TEST_USER:$TEST_PASSWORD:$AZURE_TENANT_ID:$-uses:actions/upload-artifact@v3if:always()with:name:playwright-reportpath:playwright-report/retention-days:30
// ❌ BAD: Using real credentialsconstuser='admin@company.com';constpassword='RealPassword123!';// ✅ GOOD: Using dedicated test accountsconstuser=process.env.TEST_USER;// testadmin@company.comconstpassword=process.env.TEST_PASSWORD;// Test environment only
2. Test Isolation
Clean state between tests:
1
2
3
4
5
6
7
8
9
10
11
12
13
test.beforeEach(async({page})=>{// Clear cookies and storageawaitpage.context().clearCookies();awaitpage.context().clearPermissions();// Start freshawaitpage.goto('about:blank');});test.afterEach(async({page})=>{// Logoutawaitpage.goto('https://cursor.company.com/logout');});
3. Security-First Assertions
Test security controls explicitly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
test('API requires authentication',async({request})=>{// Attempt to call API without tokenconstresponse=awaitrequest.get('/api/v1/models',{headers:{// No Authorization header}});// Should rejectexpect(response.status()).toBe(401);constbody=awaitresponse.json();expect(body.error).toContain('authentication required');});
test('Intercept and validate API calls',async({page})=>{// Listen for Azure OpenAI API callsawaitpage.route('https://**.openai.azure.com/**',asyncroute=>{constrequest=route.request();// Verify API key is presentconstapiKey=request.headers()['api-key'];expect(apiKey).toBeTruthy();expect(apiKey).toMatch(/^[a-f0-9]{32}$/);// Verify request is loggedconsole.log(`API call to: ${request.url()}`);console.log(`Headers: ${JSON.stringify(request.headers())}`);// Allow request to proceedawaitroute.continue();});// Perform action that triggers API callawaitpage.goto('https://cursor.company.com');awaitpage.fill('#prompt','Explain this code');awaitpage.click('#submit');// Wait for responseawaitpage.waitForSelector('.response');});
test('Graceful degradation under network issues',async({page,context})=>{// Simulate slow networkawaitcontext.route('**/*',route=>{setTimeout(()=>route.continue(),2000);// 2s delay});awaitpage.goto('https://cursor.company.com');// Should show loading stateawaitexpect(page.locator('.loading-spinner')).toBeVisible();// Should eventually loadawaitexpect(page.locator('.dashboard')).toBeVisible({timeout:10000});});test('Offline mode security',async({page,context})=>{awaitpage.goto('https://cursor.company.com');// Go offlineawaitcontext.setOffline(true);// Attempt to access sensitive dataawaitpage.click('text=View API Keys');// Should show offline message, not cached sensitive dataawaitexpect(page.locator('text=No internet connection')).toBeVisible();awaitexpect(page.locator('.api-key')).not.toBeVisible();});
// custom-reporter.tsimport{Reporter,TestCase,TestResult}from'@playwright/test/reporter';classSecurityReporterimplementsReporter{onTestEnd(test:TestCase,result:TestResult){if(test.tags.includes('@security')){console.log(`Security Test: ${test.title}`);console.log(`Status: ${result.status}`);console.log(`Duration: ${result.duration}ms`);if(result.status==='failed'){// Alert security teamthis.alertSecurityTeam(test,result);}}}privatealertSecurityTeam(test:TestCase,result:TestResult){// Send to Teams webhookfetch(process.env.TEAMS_WEBHOOK_URL!,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({title:'🚨 Security Test Failed',text:`${test.title} failed in ${result.duration}ms`,sections:[{activityTitle:'Test Details',facts:[{name:'Test',value:test.title},{name:'Status',value:result.status},{name:'Error',value:result.error?.message||'Unknown'}]}]})});}}exportdefaultSecurityReporter;
test('CrowdStrike detects malicious activity',async({page})=>{// Perform action that should trigger CrowdStrikeawaitpage.goto('https://cursor.company.com');// Attempt to download suspicious fileconst[download]=awaitPromise.all([page.waitForEvent('download'),page.click('a[href="/download/suspicious.exe"]')]);// CrowdStrike should block download// Verify in CrowdStrike APIconstcsResponse=awaitfetch('https://api.crowdstrike.com/detects/queries/detects/v1',{headers:{'Authorization':`Bearer ${process.env.CROWDSTRIKE_API_KEY}`}});constdetections=awaitcsResponse.json();expect(detections.resources.length).toBeGreaterThan(0);});
test('Wiz detects misconfigured resources',async()=>{// Deploy test resource with intentional misconfiguration// (in test environment only)// Wait for Wiz to scanawaitnewPromise(resolve=>setTimeout(resolve,60000));// 1 minute// Check Wiz API for findingsconstwizResponse=awaitfetch('https://api.wiz.io/findings',{headers:{'Authorization':`Bearer ${process.env.WIZ_API_KEY}`}});constfindings=awaitwizResponse.json();consttestFinding=findings.find(f=>f.resource.id==='test-resource-id');expect(testFinding).toBeTruthy();expect(testFinding.severity).toBe('high');});