diff --git a/.github/workflows/visual-preview.yml b/.github/workflows/visual-preview.yml deleted file mode 100644 index 51d1cea42..000000000 --- a/.github/workflows/visual-preview.yml +++ /dev/null @@ -1,208 +0,0 @@ -name: Visual Preview - -on: - pull_request: - paths: - - "app/**" - - "components/arcade/**" - - "components/i18n/**" - - "components/seo/**" - - "lib/website-i18n.ts" - - "public/i18n/**" - - "scripts/**" - - "package.json" - - "yarn.lock" - - "next.config.mjs" - - ".github/workflows/visual-preview.yml" - workflow_dispatch: - -permissions: - contents: read - -jobs: - screenshots: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - with: - persist-credentials: false - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version: "20" - cache: yarn - - - name: Install dependencies - run: yarn install --frozen-lockfile --non-interactive - - - name: Build - run: yarn build - - - name: Install Chromium - run: yarn playwright install --with-deps chromium - - - name: Start production server - run: | - yarn start --hostname 127.0.0.1 --port 3000 > /tmp/gcsb-web.log 2>&1 & - yarn wait-on http://127.0.0.1:3000 - - - name: Capture desktop and mobile states - run: | - mkdir -p screenshots - node <<'NODE' - const { chromium } = require('playwright') - - async function assertNoHorizontalOverflow(page, label) { - const overflow = await page.evaluate(() => - document.documentElement.scrollWidth - document.documentElement.clientWidth, - ) - if (overflow > 1) { - throw new Error(`${label} has ${overflow}px horizontal overflow`) - } - } - - ;(async () => { - const browser = await chromium.launch({ headless: true }) - const page = await browser.newPage({ viewport: { width: 1440, height: 900 } }) - - await page.goto('http://127.0.0.1:3000', { waitUntil: 'networkidle' }) - await page.getByRole('button', { name: 'Analyze profile' }).waitFor() - - const options = page.locator('.analyzer-facilitator-option') - if ((await options.count()) !== 2) { - throw new Error(`Expected two analyzer preference switches, found ${await options.count()}`) - } - - const facilitatorOption = options.filter({ - hasText: 'Participating in Facilitator Program', - }) - const autoFetchOption = options.filter({ - hasText: 'Automatically fetch latest data', - }) - await facilitatorOption.waitFor() - await autoFetchOption.waitFor() - await autoFetchOption.getByText( - 'Paste a valid public profile URL to enable this option.', - { exact: true }, - ).waitFor() - if (!(await autoFetchOption.locator('input[type="checkbox"]').isDisabled())) { - throw new Error('Auto-fetch switch should be disabled without a valid profile URL') - } - - if (await page.getByText('View program details', { exact: true }).count()) { - throw new Error('Removed program details action is still rendered') - } - - const launcher = page.locator('.facilitator-launcher') - await launcher.waitFor({ state: 'attached' }) - if (await launcher.isVisible()) { - throw new Error('Facilitator launcher is visible before participation is enabled') - } - - const languageTrigger = page.locator('.website-language-trigger') - await languageTrigger.waitFor() - if ((await languageTrigger.getAttribute('aria-expanded')) !== 'false') { - throw new Error('Language popover starts open unexpectedly') - } - - await page.screenshot({ - path: 'screenshots/arcade-desktop-empty-1440x900.png', - fullPage: false, - }) - - await languageTrigger.click() - const languageOptions = page.locator('.website-language-option') - await languageOptions.first().waitFor() - if ((await languageOptions.count()) !== 13) { - throw new Error(`Unexpected language option count: ${await languageOptions.count()}`) - } - await page.screenshot({ - path: 'screenshots/arcade-desktop-language-menu-1440x900.png', - fullPage: false, - }) - await languageTrigger.click() - - const lightPage = await browser.newPage({ viewport: { width: 1440, height: 900 } }) - await lightPage.addInitScript(() => { - window.localStorage.setItem('arcade-theme', 'light') - }) - await lightPage.goto('http://127.0.0.1:3000', { waitUntil: 'networkidle' }) - await lightPage.locator('html.light').waitFor() - await lightPage.getByRole('button', { name: 'Analyze profile' }).waitFor() - - const lightOptions = lightPage.locator('.analyzer-facilitator-option') - if ((await lightOptions.count()) !== 2) { - throw new Error(`Light theme expected two preference switches, found ${await lightOptions.count()}`) - } - for (let index = 0; index < 2; index += 1) { - const surface = lightOptions.nth(index) - await surface.waitFor() - const paint = await surface.evaluate((element) => { - const style = window.getComputedStyle(element) - return `${style.backgroundColor} ${style.backgroundImage}` - }) - if ( - paint.includes('rgb(5, 9, 24)') || - paint.includes('rgb(7, 14, 31)') || - paint.includes('rgb(10, 28, 54)') - ) { - throw new Error(`Dark preference surface in light theme: ${paint}`) - } - } - - await lightPage.screenshot({ - path: 'screenshots/arcade-desktop-light-1440x900.png', - fullPage: false, - }) - await lightPage.close() - - await page.setViewportSize({ width: 390, height: 844 }) - await page.goto('http://127.0.0.1:3000', { waitUntil: 'networkidle' }) - await page.getByRole('button', { name: 'Analyze profile' }).waitFor() - await assertNoHorizontalOverflow(page, 'English mobile homepage') - await page.screenshot({ - path: 'screenshots/arcade-mobile-empty-390x844.png', - fullPage: false, - }) - - await page.locator('.website-language-trigger').click() - await page - .locator('.website-language-option') - .filter({ hasText: 'Tiếng Việt' }) - .click() - await page.waitForURL((url) => /\/vi\/?$/.test(url.pathname)) - await page.locator('html[lang="vi"][dir="ltr"][data-locale="vi"]').waitFor() - await page.getByRole('button', { name: 'Phân tích hồ sơ' }).waitFor() - const viAutoFetchOption = page.locator('.analyzer-facilitator-option').filter({ - hasText: 'Tự động lấy dữ liệu mới nhất', - }) - await viAutoFetchOption.waitFor() - await viAutoFetchOption.getByText( - 'Dán URL hồ sơ công khai hợp lệ để bật tùy chọn này.', - { exact: true }, - ).waitFor() - if (!(await viAutoFetchOption.locator('input[type="checkbox"]').isDisabled())) { - throw new Error('Vietnamese auto-fetch switch should be disabled without a valid profile URL') - } - await assertNoHorizontalOverflow(page, 'Vietnamese mobile homepage') - await page.screenshot({ - path: 'screenshots/arcade-mobile-vi-390x844.png', - fullPage: false, - }) - - await browser.close() - })().catch((error) => { - console.error(error) - process.exit(1) - }) - NODE - - - name: Upload screenshots - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: arcade-visual-preview - path: screenshots/*.png - retention-days: 14