Comprehensive Playwright Automation Testing & Findings
📍 http://cortexone-internal.rival.io:3131/login
🗓️ December 14, 2025
🔬 Playwright v1.57.0 • Chromium Browser
Automated testing using Playwright automation agent with multi-layer evaluation capabilities
Form elements, navigation, component detection, screenshot capture across viewports
Header validation, cookie inspection, HTTPS verification, data exposure checks
Keyboard navigation, ARIA attributes, color contrast, alt text verification
Mobile (375px, 667px), Tablet (768px), Desktop (1920px), Landscape testing
Page load metrics, resource analysis, console error detection, FCP/LCP measurement
Framework detection, bundle analysis, OAuth integration, third-party services
Authentication: Descope (Web Components)
OAuth: Google, GitHub
Analytics: Google Analytics
Framework: Custom Web Components (DESCOPE-WC)
Application is vulnerable to multiple attack vectors due to absence of essential security headers
Vulnerability to: Clickjacking, XSS, MIME sniffing, Frame-based attacks
| Header | Purpose | Impact if Missing |
|---|---|---|
X-Frame-Options |
Prevent clickjacking | Page can be embedded in malicious frames |
X-Content-Type-Options |
Prevent MIME sniffing | Browser may execute files as wrong type |
Strict-Transport-Security |
Force HTTPS | Vulnerable to MITM attacks on first visit |
Content-Security-Policy |
Prevent XSS | Inline scripts can be injected |
// middleware/security-headers.ts
import { Request, Response, NextFunction } from 'express';
export function securityHeaders(req: Request, res: Response, next: NextFunction) {
// Prevent clickjacking
res.setHeader('X-Frame-Options', 'DENY');
// Prevent MIME sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
// Enforce HTTPS
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
// Content Security Policy
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'none';"
);
// XSS Protection (legacy)
res.setHeader('X-XSS-Protection', '1; mode=block');
// Referrer Policy
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// Permissions Policy
res.setHeader(
'Permissions-Policy',
'geolocation=(), microphone=(), camera=(), payment=()'
);
next();
}
// app.ts - Add middleware to express app
app.use(securityHeaders);
Authentication credentials transmitted over unencrypted connection - major security vulnerability
Current: http://cortexone-internal.rival.io:3131/login
// nginx.conf - SSL Configuration
server {
listen 443 ssl http2;
server_name cortexone-internal.rival.io;
# SSL Certificate Configuration
ssl_certificate /etc/nginx/ssl/rival.crt;
ssl_certificate_key /etc/nginx/ssl/rival.key;
# SSL Protocol and Ciphers
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# HSTS Header
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Security headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
proxy_pass http://localhost:3131;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# HTTP Redirect to HTTPS
server {
listen 80;
server_name cortexone-internal.rival.io;
return 301 https://$server_name$request_uri;
}
WCAG 2.1 AA violation - page content exceeds mobile viewport width
Affects mobile viewport: 375x667px
/* Identify and fix mobile overflow */
/* Option 1: Add responsive container */
.login-container {
max-width: 100%;
width: 100vw;
overflow-x: hidden;
padding: 20px;
}
.form-wrapper {
width: 100%;
max-width: 400px;
margin: 0 auto;
}
/* Option 2: Fix specific element causing overflow */
img {
max-width: 100%;
height: auto;
display: block;
}
/* Option 3: Add mobile breakpoint */
@media (max-width: 375px) {
.login-container {
padding: 15px;
margin: 0;
}
form {
width: 100%;
}
input,
button {
width: 100%;
font-size: 16px; /* Prevents zoom on iOS */
}
.oauth-buttons {
display: flex;
flex-direction: column;
gap: 10px;
}
.oauth-buttons button {
width: 100%;
}
}
/* Option 4: Ensure viewport meta tag is correct */
HTML: <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
WCAG 2.1 A - Screen reader users cannot identify logo (Criterion 1.1.1)
WCAG Criterion 1.1.1 Non-text Content (Level A)
<img src="rival-logo.png" />
<img src="rival-logo.png" alt="Rival" height="30" width="auto" />
role="presentation" for purely decorative imagesImprove user feedback with real-time validation and aria-invalid states
Affects user experience and accessibility
// validation.ts - Real-time form validation
export class LoginFormValidator {
private emailInput: HTMLInputElement;
private passwordInput: HTMLInputElement;
constructor() {
this.emailInput = document.querySelector('input[type="email"]')!;
this.passwordInput = document.querySelector('input[type="password"]')!;
}
validateEmail(email: string): boolean {
const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
return emailRegex.test(email);
}
validatePassword(password: string): boolean {
// At least 8 characters, 1 uppercase, 1 lowercase, 1 number
return password.length >= 8 &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/[0-9]/.test(password);
}
init() {
this.emailInput.addEventListener('blur', () => this.validateEmailField());
this.emailInput.addEventListener('input', () => this.validateEmailField());
this.passwordInput.addEventListener('blur', () => this.validatePasswordField());
this.passwordInput.addEventListener('input', () => this.validatePasswordField());
}
private validateEmailField() {
const email = this.emailInput.value;
const isValid = !email || this.validateEmail(email);
if (!isValid) {
this.emailInput.setAttribute('aria-invalid', 'true');
this.emailInput.classList.add('invalid');
this.showError(this.emailInput, 'Invalid email format');
} else {
this.emailInput.setAttribute('aria-invalid', 'false');
this.emailInput.classList.remove('invalid');
this.clearError(this.emailInput);
}
}
private validatePasswordField() {
const password = this.passwordInput.value;
const isValid = !password || this.validatePassword(password);
if (!isValid) {
this.passwordInput.setAttribute('aria-invalid', 'true');
this.passwordInput.classList.add('invalid');
this.showError(
this.passwordInput,
'Password must be 8+ chars with uppercase, lowercase, and numbers'
);
} else {
this.passwordInput.setAttribute('aria-invalid', 'false');
this.passwordInput.classList.remove('invalid');
this.clearError(this.passwordInput);
}
}
private showError(input: HTMLInputElement, message: string) {
const errorId = `${input.id}-error`;
let errorElement = document.getElementById(errorId);
if (!errorElement) {
errorElement = document.createElement('div');
errorElement.id = errorId;
errorElement.role = 'alert';
errorElement.setAttribute('aria-live', 'polite');
errorElement.className = 'error-message';
input.parentElement?.appendChild(errorElement);
}
errorElement.textContent = message;
input.setAttribute('aria-describedby', errorId);
}
private clearError(input: HTMLInputElement) {
const errorId = `${input.id}-error`;
const errorElement = document.getElementById(errorId);
if (errorElement) {
errorElement.remove();
}
input.removeAttribute('aria-describedby');
}
}
/* Validation states */
input[aria-invalid="true"] {
border-color: #dc2626;
background-color: #fee2e2;
}
input[aria-invalid="false"] {
border-color: #10b981;
background-color: #ecfdf5;
}
.error-message {
color: #dc2626;
font-size: 0.875rem;
margin-top: 4px;
display: block;
}
.invalid {
border-color: #dc2626 !important;
}
Effort: 4-8 hours (Infrastructure)
Impact: Protects credentials
Status: Blocking
Effort: 2-4 hours (Backend)
Impact: Prevents common attacks
Status: Blocking
Effort: 1-2 hours (Frontend)
Impact: WCAG compliance, UX
Status: Active
Effort: 2-4 hours (Frontend)
Impact: UX, accessibility
Status: Active
Effort: 5 minutes (Trivial)
Impact: Quick win
Status: Ready
Effort: 1-2 hours (Frontend)
Impact: Accessibility
Status: Ready
40 automated tests executed with detailed findings and recommendations
All issues documented with BDD acceptance criteria and implementation code
Clear priorities, effort estimates, and concrete code examples for each fix
Test Date: December 14, 2025
Framework: Playwright v1.57.0
Overall Grade: C+ (Functional • Needs Security Hardening)
This report was generated by the Playwright E2E Test Automation Agent
with comprehensive evaluation of security, accessibility, and responsive design.