- Create modern toast notification system replacing browser alerts - Add mobile-responsive layouts with touch-friendly elements - Implement loading states and progress indicators for all AJAX operations - Add mobile navigation with collapsible menus - Create enhanced form validation with inline error messages - Add accessibility features (keyboard navigation, ARIA labels) - Build comprehensive mobile testing suite - Optimize for 320px to 1024px+ screen sizes - Include progressive enhancement and fallback support 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
285 lines
No EOL
11 KiB
TypeScript
285 lines
No EOL
11 KiB
TypeScript
/**
|
|
* Mobile Responsiveness Test for HVAC Plugin UX Enhancements
|
|
*
|
|
* Tests the mobile experience including:
|
|
* - Touch-friendly interface elements
|
|
* - Responsive layouts
|
|
* - Mobile navigation
|
|
* - Toast notifications on mobile
|
|
* - Form usability on mobile devices
|
|
*/
|
|
|
|
import { test, expect, devices } from '@playwright/test';
|
|
|
|
const STAGING_URL = 'https://upskill-staging.measurequick.com';
|
|
|
|
// Test with different mobile viewports
|
|
const mobileDevices = [
|
|
devices['iPhone 12'],
|
|
devices['iPhone 12 Pro'],
|
|
devices['Samsung Galaxy S21'],
|
|
devices['iPad Mini']
|
|
];
|
|
|
|
for (const device of mobileDevices) {
|
|
test.describe(`Mobile Responsiveness - ${device.name}`, () => {
|
|
test.use({ ...device });
|
|
|
|
test(`Login page mobile layout - ${device.name}`, async ({ page }) => {
|
|
test.setTimeout(20000);
|
|
|
|
await page.goto(`${STAGING_URL}/community-login`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check login form is properly sized for mobile
|
|
const loginForm = page.locator('#hvac_community_loginform');
|
|
await expect(loginForm).toBeVisible();
|
|
|
|
// Verify mobile-optimized form elements
|
|
const usernameField = page.locator('#user_login');
|
|
const passwordField = page.locator('#user_pass');
|
|
const submitButton = page.locator('#wp-submit');
|
|
|
|
await expect(usernameField).toBeVisible();
|
|
await expect(passwordField).toBeVisible();
|
|
await expect(submitButton).toBeVisible();
|
|
|
|
// Check touch target sizes (minimum 44px for iOS)
|
|
const submitBox = await submitButton.boundingBox();
|
|
expect(submitBox?.height).toBeGreaterThanOrEqual(44);
|
|
|
|
// Test form field focus states work on mobile
|
|
await usernameField.focus();
|
|
await passwordField.focus();
|
|
|
|
// Take screenshot for visual verification
|
|
await page.screenshot({ path: `test-results/mobile-login-${device.name?.replace(/\s+/g, '-')}.png` });
|
|
});
|
|
|
|
test(`Dashboard mobile layout - ${device.name}`, async ({ page }) => {
|
|
test.setTimeout(30000);
|
|
|
|
// Login first
|
|
await page.goto(`${STAGING_URL}/community-login`);
|
|
await page.fill('#user_login', 'test_trainer');
|
|
await page.fill('#user_pass', 'Test123!');
|
|
await page.click('#wp-submit');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Verify we're on dashboard
|
|
await expect(page).toHaveURL(/hvac-dashboard/);
|
|
|
|
// Check dashboard layout adapts to mobile
|
|
const dashboard = page.locator('.hvac-dashboard-wrapper');
|
|
await expect(dashboard).toBeVisible();
|
|
|
|
// Test mobile navigation if present
|
|
const mobileNavToggle = page.locator('.hvac-mobile-nav-toggle');
|
|
if (await mobileNavToggle.isVisible()) {
|
|
await mobileNavToggle.click();
|
|
|
|
const mobileNav = page.locator('.hvac-mobile-nav');
|
|
await expect(mobileNav).toHaveClass(/open/);
|
|
|
|
// Test navigation links work
|
|
const navLinks = page.locator('.hvac-mobile-nav a');
|
|
const navCount = await navLinks.count();
|
|
expect(navCount).toBeGreaterThan(0);
|
|
}
|
|
|
|
// Check stats cards stack properly on mobile
|
|
const statCards = page.locator('.hvac-stat-card');
|
|
const statCount = await statCards.count();
|
|
if (statCount > 0) {
|
|
// Verify cards are visible and properly sized
|
|
for (let i = 0; i < Math.min(statCount, 3); i++) {
|
|
await expect(statCards.nth(i)).toBeVisible();
|
|
}
|
|
}
|
|
|
|
// Test touch scrolling works
|
|
await page.evaluate(() => window.scrollTo(0, 200));
|
|
await page.waitForTimeout(500);
|
|
|
|
// Take screenshot
|
|
await page.screenshot({ path: `test-results/mobile-dashboard-${device.name?.replace(/\s+/g, '-')}.png` });
|
|
});
|
|
|
|
test(`Toast notifications on mobile - ${device.name}`, async ({ page }) => {
|
|
test.setTimeout(25000);
|
|
|
|
// Login
|
|
await page.goto(`${STAGING_URL}/community-login`);
|
|
await page.fill('#user_login', 'test_trainer');
|
|
await page.fill('#user_pass', 'Test123!');
|
|
await page.click('#wp-submit');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Go to certificate reports to trigger potential notifications
|
|
await page.goto(`${STAGING_URL}/certificate-reports`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check if toast container exists and is positioned correctly for mobile
|
|
const toastContainer = page.locator('.hvac-toast-container');
|
|
|
|
// Inject test toast to verify mobile positioning
|
|
await page.evaluate(() => {
|
|
if (window.HVACToast) {
|
|
window.HVACToast.success('This is a test toast notification for mobile testing');
|
|
}
|
|
});
|
|
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Check if toast is visible and properly positioned
|
|
const toast = page.locator('.hvac-toast');
|
|
if (await toast.count() > 0) {
|
|
await expect(toast.first()).toBeVisible();
|
|
|
|
// Verify toast doesn't overflow viewport
|
|
const toastBox = await toast.first().boundingBox();
|
|
const viewport = page.viewportSize();
|
|
|
|
if (toastBox && viewport) {
|
|
expect(toastBox.x + toastBox.width).toBeLessThanOrEqual(viewport.width);
|
|
expect(toastBox.y).toBeGreaterThanOrEqual(0);
|
|
}
|
|
}
|
|
|
|
// Take screenshot
|
|
await page.screenshot({ path: `test-results/mobile-toast-${device.name?.replace(/\s+/g, '-')}.png` });
|
|
});
|
|
|
|
test(`Form interaction on mobile - ${device.name}`, async ({ page }) => {
|
|
test.setTimeout(25000);
|
|
|
|
// Login
|
|
await page.goto(`${STAGING_URL}/community-login`);
|
|
await page.fill('#user_login', 'test_trainer');
|
|
await page.fill('#user_pass', 'Test123!');
|
|
await page.click('#wp-submit');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Go to create event page to test forms
|
|
await page.goto(`${STAGING_URL}/manage-event`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Test form field interactions
|
|
const titleField = page.locator('#event_title');
|
|
if (await titleField.isVisible()) {
|
|
// Check field is properly sized for mobile
|
|
const fieldBox = await titleField.boundingBox();
|
|
expect(fieldBox?.height).toBeGreaterThanOrEqual(44);
|
|
|
|
// Test touch interaction
|
|
await titleField.tap();
|
|
await titleField.fill('Mobile Test Event');
|
|
|
|
const value = await titleField.inputValue();
|
|
expect(value).toBe('Mobile Test Event');
|
|
}
|
|
|
|
// Check form buttons are touch-friendly
|
|
const buttons = page.locator('button, input[type="submit"]');
|
|
const buttonCount = await buttons.count();
|
|
|
|
for (let i = 0; i < Math.min(buttonCount, 3); i++) {
|
|
const button = buttons.nth(i);
|
|
if (await button.isVisible()) {
|
|
const buttonBox = await button.boundingBox();
|
|
expect(buttonBox?.height).toBeGreaterThanOrEqual(44);
|
|
}
|
|
}
|
|
|
|
// Take screenshot
|
|
await page.screenshot({ path: `test-results/mobile-form-${device.name?.replace(/\s+/g, '-')}.png` });
|
|
});
|
|
});
|
|
}
|
|
|
|
// Test responsive breakpoints
|
|
test.describe('Responsive Breakpoints', () => {
|
|
const breakpoints = [
|
|
{ name: 'Mobile Small', width: 320, height: 568 },
|
|
{ name: 'Mobile Medium', width: 375, height: 667 },
|
|
{ name: 'Mobile Large', width: 414, height: 896 },
|
|
{ name: 'Tablet Portrait', width: 768, height: 1024 },
|
|
{ name: 'Tablet Landscape', width: 1024, height: 768 }
|
|
];
|
|
|
|
for (const breakpoint of breakpoints) {
|
|
test(`Dashboard layout at ${breakpoint.name} (${breakpoint.width}x${breakpoint.height})`, async ({ page }) => {
|
|
test.setTimeout(25000);
|
|
|
|
// Set viewport
|
|
await page.setViewportSize({ width: breakpoint.width, height: breakpoint.height });
|
|
|
|
// Login
|
|
await page.goto(`${STAGING_URL}/community-login`);
|
|
await page.fill('#user_login', 'test_trainer');
|
|
await page.fill('#user_pass', 'Test123!');
|
|
await page.click('#wp-submit');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check dashboard layout
|
|
await expect(page).toHaveURL(/hvac-dashboard/);
|
|
|
|
// Verify content is visible and accessible
|
|
const mainContent = page.locator('.hvac-dashboard-wrapper, .entry-content');
|
|
await expect(mainContent).toBeVisible();
|
|
|
|
// Check navigation is appropriate for screen size
|
|
if (breakpoint.width <= 767) {
|
|
// Mobile: should see mobile nav or stacked nav
|
|
const mobileNav = page.locator('.hvac-mobile-nav-toggle, .hvac-dashboard-nav');
|
|
await expect(mobileNav).toBeVisible();
|
|
} else {
|
|
// Tablet/Desktop: should see regular nav
|
|
const desktopNav = page.locator('.hvac-dashboard-nav');
|
|
await expect(desktopNav).toBeVisible();
|
|
}
|
|
|
|
// Take screenshot
|
|
await page.screenshot({
|
|
path: `test-results/responsive-${breakpoint.name.replace(/\s+/g, '-')}-${breakpoint.width}x${breakpoint.height}.png`,
|
|
fullPage: true
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
// Test accessibility on mobile
|
|
test.describe('Mobile Accessibility', () => {
|
|
test.use(devices['iPhone 12']);
|
|
|
|
test('Touch accessibility and keyboard navigation', async ({ page }) => {
|
|
test.setTimeout(25000);
|
|
|
|
// Login
|
|
await page.goto(`${STAGING_URL}/community-login`);
|
|
await page.fill('#user_login', 'test_trainer');
|
|
await page.fill('#user_pass', 'Test123!');
|
|
await page.click('#wp-submit');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Test keyboard navigation with tab
|
|
await page.keyboard.press('Tab');
|
|
await page.keyboard.press('Tab');
|
|
await page.keyboard.press('Tab');
|
|
|
|
// Check focus states are visible
|
|
const focusedElement = page.locator(':focus');
|
|
await expect(focusedElement).toBeVisible();
|
|
|
|
// Test escape key closes mobile elements
|
|
await page.keyboard.press('Escape');
|
|
|
|
// Verify aria labels exist on interactive elements
|
|
const buttons = page.locator('button[aria-label], input[aria-label]');
|
|
const buttonCount = await buttons.count();
|
|
console.log(`Found ${buttonCount} buttons with aria-labels`);
|
|
|
|
// Take screenshot
|
|
await page.screenshot({ path: `test-results/mobile-accessibility.png` });
|
|
});
|
|
}); |