|
1 | 1 | import { expect, test } from "@playwright/test"; |
| 2 | +import type { Page } from "@playwright/test"; |
| 3 | + |
| 4 | +const responsiveViewports = [ |
| 5 | + { height: 900, name: "large desktop", width: 1440 }, |
| 6 | + { height: 768, name: "laptop", width: 1366 }, |
| 7 | + { height: 720, name: "compact laptop", width: 1280 }, |
| 8 | + { height: 768, name: "tablet landscape", width: 1024 }, |
| 9 | + { height: 1024, name: "tablet portrait", width: 768 }, |
| 10 | + { height: 844, name: "mobile", width: 390 }, |
| 11 | + { height: 667, name: "small mobile", width: 375 }, |
| 12 | +]; |
| 13 | + |
| 14 | +async function expectNoHorizontalPageScroll(page: Page) { |
| 15 | + const hasHorizontalScroll = await page.evaluate( |
| 16 | + () => document.documentElement.scrollWidth > window.innerWidth + 1, |
| 17 | + ); |
| 18 | + |
| 19 | + expect(hasHorizontalScroll).toBe(false); |
| 20 | +} |
| 21 | + |
| 22 | +async function expectNoMainInternalScroll(page: Page) { |
| 23 | + const hasInternalScroll = await page |
| 24 | + .locator("main > div") |
| 25 | + .evaluate((element) => element.scrollHeight > element.clientHeight + 1); |
| 26 | + |
| 27 | + expect(hasInternalScroll).toBe(false); |
| 28 | +} |
| 29 | + |
| 30 | +async function expectFrameSpacing(page: Page, viewportWidth: number) { |
| 31 | + const frameLeft = await page |
| 32 | + .getByRole("main") |
| 33 | + .evaluate( |
| 34 | + (main) => main.parentElement?.parentElement?.getBoundingClientRect().left, |
| 35 | + ); |
| 36 | + |
| 37 | + expect(frameLeft ?? 0).toBeGreaterThanOrEqual( |
| 38 | + viewportWidth >= 1280 ? 47 : 31, |
| 39 | + ); |
| 40 | +} |
| 41 | + |
| 42 | +async function expectTabsAlignWithPanel(page: Page) { |
| 43 | + const isAligned = await page.evaluate(() => { |
| 44 | + const nav = document.querySelector('nav[aria-label="Primary"]'); |
| 45 | + const main = document.querySelector("main"); |
| 46 | + |
| 47 | + if (!nav || !main) { |
| 48 | + return false; |
| 49 | + } |
| 50 | + |
| 51 | + const navRect = nav.getBoundingClientRect(); |
| 52 | + const mainRect = main.getBoundingClientRect(); |
| 53 | + |
| 54 | + return ( |
| 55 | + Math.abs(navRect.left - mainRect.left) <= 2 && |
| 56 | + Math.abs(navRect.right - mainRect.right) <= 2 |
| 57 | + ); |
| 58 | + }); |
| 59 | + |
| 60 | + expect(isAligned).toBe(true); |
| 61 | +} |
2 | 62 |
|
3 | 63 | test("homepage loads", async ({ page }) => { |
4 | 64 | await page.goto("/"); |
@@ -66,3 +126,73 @@ test("mobile viewport smoke test", async ({ page }) => { |
66 | 126 | page.getByText(/I work on practical web projects/i), |
67 | 127 | ).toBeVisible(); |
68 | 128 | }); |
| 129 | + |
| 130 | +for (const viewport of responsiveViewports) { |
| 131 | + test(`home responsive layout at ${viewport.name}`, async ({ page }) => { |
| 132 | + await page.setViewportSize({ |
| 133 | + width: viewport.width, |
| 134 | + height: viewport.height, |
| 135 | + }); |
| 136 | + await page.goto("/"); |
| 137 | + |
| 138 | + await expect( |
| 139 | + page.getByRole("heading", { name: /Tadas Baltrūnas/i }), |
| 140 | + ).toBeVisible(); |
| 141 | + await expect( |
| 142 | + page.getByRole("main").getByRole("link", { name: /View projects/i }), |
| 143 | + ).toBeVisible(); |
| 144 | + await expect( |
| 145 | + page.getByRole("main").getByRole("link", { name: /Read Blog/i }), |
| 146 | + ).toBeVisible(); |
| 147 | + await expectNoHorizontalPageScroll(page); |
| 148 | + await expectTabsAlignWithPanel(page); |
| 149 | + |
| 150 | + if (viewport.width >= 1024) { |
| 151 | + await expectNoMainInternalScroll(page); |
| 152 | + await expectFrameSpacing(page, viewport.width); |
| 153 | + } |
| 154 | + }); |
| 155 | + |
| 156 | + test(`about responsive layout at ${viewport.name}`, async ({ page }) => { |
| 157 | + await page.setViewportSize({ |
| 158 | + width: viewport.width, |
| 159 | + height: viewport.height, |
| 160 | + }); |
| 161 | + await page.goto("/about"); |
| 162 | + |
| 163 | + await expect( |
| 164 | + page.getByRole("heading", { name: /What I am up to/i }), |
| 165 | + ).toBeVisible(); |
| 166 | + await expect( |
| 167 | + page.getByAltText(/Portrait of Tadas Baltrūnas/i), |
| 168 | + ).toBeVisible(); |
| 169 | + await expect(page.getByRole("link", { name: /GitHub/i })).toBeVisible(); |
| 170 | + await expectNoHorizontalPageScroll(page); |
| 171 | + await expectTabsAlignWithPanel(page); |
| 172 | + |
| 173 | + if (viewport.width >= 1024) { |
| 174 | + await expectNoMainInternalScroll(page); |
| 175 | + await expectFrameSpacing(page, viewport.width); |
| 176 | + } |
| 177 | + }); |
| 178 | +} |
| 179 | + |
| 180 | +for (const viewport of responsiveViewports) { |
| 181 | + test(`content pages do not overflow at ${viewport.name}`, async ({ |
| 182 | + page, |
| 183 | + }) => { |
| 184 | + await page.setViewportSize({ |
| 185 | + width: viewport.width, |
| 186 | + height: viewport.height, |
| 187 | + }); |
| 188 | + |
| 189 | + for (const path of ["/projects", "/blog", "/stack", "/about"]) { |
| 190 | + await page.goto(path); |
| 191 | + |
| 192 | + await expect(page.getByRole("link", { name: /^Home$/i })).toBeVisible(); |
| 193 | + await expect(page.getByRole("link", { name: /^About$/i })).toBeVisible(); |
| 194 | + await expectNoHorizontalPageScroll(page); |
| 195 | + await expectTabsAlignWithPanel(page); |
| 196 | + } |
| 197 | + }); |
| 198 | +} |
0 commit comments