Skip to main content

Azul Coding
Test Your Site with Playwright in 10 Minutes

tests.spec.ts

// AZUL CODING ---------------------------------------
// Test Your Site with Playwright in 10 Minutes
// https://youtu.be/lyXa9KzUl1E


import { test, expect } from "@playwright/test";

test.describe("Main Page", () => {
    test.beforeEach(async ({ page }) => {
        await page.goto("/");
    });

    test("should display the correct title", async ({ page }) => {
        await expect(page).toHaveTitle("Azul");
    });

    test("should have working navigation to about page", async ({ page }) => {
        await page.getByText("About").click();
        await expect(page).toHaveURL(/.*about.html/);
        await expect(page.locator("h1")).toContainText("About Us");
    });

    test("should handle contact form submission", async ({ page }) => {
        await page.route("/api/contact", async (route) => {
            await route.fulfill({
                status: 200,
                body: JSON.stringify({ success: true })
            });       
        });

        await page.locator('button[type="submit"]').click();
        await expect(page.locator("#name-error")).toBeVisible();
        await expect(page.locator("#email-error")).toBeVisible();

        await page.locator("#name").fill("John Doe");
        await page.locator("#email").fill("john@example.com");
        await page.locator("#message").fill("Test message");
        await page.locator('button[type="submit"]').click();

        await expect(page.locator("#success-message")).toBeVisible();
    });

    test("should handle server error gracefully", async ({ page }) => {
        await page.route("/api/contact", async (route) => {
            await route.fulfill({
                status: 500,
                body: JSON.stringify({ error: "Server error" })
            });       
        });

        await page.locator("#name").fill("John Doe");
        await page.locator("#email").fill("john@example.com");
        await page.locator("#message").fill("Test message");
        await page.locator('button[type="submit"]').click();

        await expect(page.locator("#error-message")).toBeVisible();
    });
});

test.describe("About Page", () => {
    test.beforeEach(async ({ page }) => {
        await page.goto("/about.html");
    });

    test("should display the correct title", async ({ page }) => {
        await expect(page).toHaveTitle("About Us");
    });

    test("should have working navigation back to main page", async ({ page }) => {
        await page.getByText("Home").click();
        await expect(page).toHaveURL(/.*index.html/);
    });
});

test.describe("Responsive Design", () => {
    test("should display mobile menu on small screens", async ({ page }) => {
        await page.setViewportSize({ width: 375, height: 667 });
        await page.goto("/");

        const mobileMenu = page.locator("#mobile-menu");
        await expect(mobileMenu).toBeVisible();

        await mobileMenu.click();
        const navLinks = page.locator("nav a:first-child");
        await expect(navLinks).toBeVisible();

        await page.screenshot({ path: "mobile-menu.png" });
    });
});

test.describe("Accessibility", () => {
    test("should have basic accessibility features", async ({ page }) => {
        await page.goto("/");
        const h1Count = await page.locator("h1").count();
        expect(h1Count).toBe(1);

        await page.goto("/about.html");
        const aboutH1Count = await page.locator("h1").count();
        expect(aboutH1Count).toBe(1);

        const images = page.locator("img");
        for (const image of await images.all())
            await expect(image).toHaveAttribute("alt", /.+/);
    });
});

Enjoying this tutorial?


script.js

// AZUL CODING ---------------------------------------
// Test Your Site with Playwright in 10 Minutes
// https://youtu.be/lyXa9KzUl1E


const mobileMenuBtn = document.getElementById("mobile-menu");
const navLinks = document.querySelector(".nav-links");

mobileMenuBtn.addEventListener("click", () => {
    navLinks.classList.toggle("active");
});

const contactForm = document.getElementById("contact-form");
contactForm?.addEventListener("submit", async (e) => {
    e.preventDefault();
    const nameError = document.getElementById("name-error");
    const emailError = document.getElementById("email-error");
    const successMessage = document.getElementById("success-message");
    const errorMessage = document.getElementById("error-message");

    nameError.classList.add("hidden");
    emailError.classList.add("hidden");
    successMessage.classList.add("hidden");
    errorMessage.classList.add("hidden");

    const name = contactForm.name.value;
    const email = contactForm.email.value;

    let hasError = false;
    if (!name) {
        nameError.classList.remove("hidden");
        hasError = true;
    }
    if (!email || !email.includes("@")) {
        emailError.classList.remove("hidden");
        hasError = true;
    }

    if (!hasError) {
        try {
            const response = await fetch("/api/contact", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({ name, email, message: contactForm.message.value }),
            });

            if (response.ok) {
                successMessage.classList.remove("hidden");
                contactForm.reset();
            } else {
                errorMessage.classList.remove("hidden");
            }
        } catch (error) {
            errorMessage.classList.remove("hidden");
        }
    }
});

index.html

<!-- AZUL CODING --------------------------------------- -->
<!-- Test Your Site with Playwright in 10 Minutes -->
<!-- https://youtu.be/lyXa9KzUl1E -->


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Azul Coding</title>
        <link rel="stylesheet" href="/assets/styles.css">
        <script src="/assets/script.js" defer></script>
    </head>
    <body>
        <header>
            <nav>
                <div class="container nav-container">
                    <div class="logo">Azul Coding</div>
                    
                    <button id="mobile-menu">
                        <svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
                        </svg>
                    </button>

                    <div class="nav-links">
                        <a href="/index.html">Home</a>
                        <a href="/about.html">About</a>
                    </div>
                </div>
            </nav>
        </header>

        <main id="main-content">
            <div class="container">
                <h1>Welcome to Azul Coding</h1>
                <p>This is a sample website that demonstrates various features for Playwright testing.</p>

                <section class="contact-section">
                    <h2>Contact Us</h2>
                    <form id="contact-form">
                        <div class="form-group">
                            <label for="name">Name</label>
                            <input type="text" id="name" name="name" autocomplete="off">
                            <div id="name-error" class="hidden">Name is required</div>
                        </div>
                        
                        <div class="form-group">
                            <label for="email">Email</label>
                            <input type="email" id="email" name="email" autocomplete="off">
                            <div id="email-error" class="hidden">Valid email is required</div>
                        </div>
                        
                        <div class="form-group">
                            <label for="message">Message</label>
                            <textarea id="message" name="message" rows="4"></textarea>
                        </div>
                        
                        <button type="submit">Send Message</button>
                        
                        <div id="success-message" class="hidden">Message sent successfully</div>
                        <div id="error-message" class="hidden">An error occurred. Please try again.</div>
                    </form>
                </section>
            </div>
        </main>
    </body>
</html>

about.html

<!-- AZUL CODING --------------------------------------- -->
<!-- Test Your Site with Playwright in 10 Minutes -->
<!-- https://youtu.be/lyXa9KzUl1E -->


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>About Us</title>
        <link rel="stylesheet" href="/assets/styles.css">
        <script src="/assets/script.js" defer></script>
    </head>
    <body>
        <header>
            <nav>
                <div class="container nav-container">
                    <div class="logo">Azul Coding</div>
                    
                    <button id="mobile-menu">
                        <svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
                        </svg>
                    </button>

                    <div class="nav-links">
                        <a href="/index.html">Home</a>
                        <a href="/about.html">About</a>
                    </div>
                </div>
            </nav>
        </header>

        <main id="main-content">
            <div class="container">
                <h1>About Us</h1>
                <p>We are a company dedicated to creating great software and helping developers test their applications effectively.</p>
                
                <section class="team-section">
                    <h2>Our Team</h2>
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras tempus vel ipsum in posuere. In massa sem, bibendum a vehicula tempus, efficitur vel ex. In hac habitasse platea dictumst. Praesent id tellus hendrerit, placerat ligula a, elementum dui. Cras suscipit ipsum eget enim condimentum, quis semper ex porttitor.</p>
                </section>
                
                <section class="mission-section">
                    <h2>Our Mission</h2>
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras tempus vel ipsum in posuere. In massa sem, bibendum a vehicula tempus, efficitur vel ex. In hac habitasse platea dictumst. Praesent id tellus hendrerit, placerat ligula a, elementum dui.</p>
                    <img src="/assets/code.jpg" alt="Code on a screen">
                </section>
            </div>
        </main>
    </body>
</html>

styles.css

/* AZUL CODING --------------------------------------- */
/* Test Your Site with Playwright in 10 Minutes */
/* https://youtu.be/lyXa9KzUl1E */


* {
    font-family: "Inter", "Helvetica", "Arial", sans-serif;
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
body {
    line-height: 1.6;
    color: white;
    background-color: #03506e;
}
.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 20px;
}
header {
    background: #49c8fc;
}
nav {
    padding: 16px 0;
}
nav .nav-container {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.logo {
    font-size: 24px;
    font-weight: 600;
    color: #2c3e50;
}

.nav-links {
    display: flex;
    gap: 32px;
}
.nav-links a {
    text-decoration: none;
    color: #2c3e50;
}
.nav-links a:hover {
    text-decoration: underline;
}
#mobile-menu {
    display: none;
    background: none;
    border: none;
    cursor: pointer;
    padding: 5px 10px;
    line-height: 0;
}

main {
    padding: 32px 0;
}
h1 {
    font-size: 32px;
}
h2 {
    padding-top: 20px;
}
p {
    margin-bottom: 15px;
    font-size: 16px;
}

form {
    max-width: 500px;
    margin: 15px 0;
}
.form-group {
    margin-bottom: 20px;
}
label {
    display: block;
    margin-bottom: 10px;
    font-weight: 500;
}
input, textarea {
    width: 100%;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 16px;
}
input:focus, textarea:focus {
    outline: 2px solid #49c8fc;
    outline-offset: 2px;
}
button {
    background-color: #49c8fc;
    color: #03506e;
    font-weight: 600;
    padding: 12px 18px;
    border: none;
    border-radius: 4px;
    font-size: 16px;
    cursor: pointer;
    transition: background-color 0.5s;
}
button:hover {
    background-color: white;
}

[id$="-error"], #error-message, #success-message {
    color: #4f0700;
    background-color: #ffc4c4;
    font-size: 15px;
    margin-top: 8px;
    padding: 5px 10px;
    border-radius: 4px;
}
#success-message {
    color: #005925;
    background-color: #ebffe2;
}

.hidden {
    display: none !important;
}
img {
    max-width: 100%;
    height: auto;
    border-radius: 10px;
    margin: 25px 0;
}

@media (max-width: 768px) {
    #mobile-menu {
        display: block;
    }

    .nav-links {
        display: none;
        position: absolute;
        top: 100%;
        left: 0;
        right: 0;
        background: white;
        padding: 16px;
        flex-direction: column;
        gap: 16px;
        border: 5px solid #49c8fc;
        margin-top: 16px;
    }
    .nav-links.active {
        display: flex;
    }
    nav .nav-container {
        position: relative;
    }

    h1 {
        font-size: 32px;
    }
}