Skip to content

Basic CMS fetching Playwright tests#153

Open
RemesTop wants to merge 2 commits into
serveri:masterfrom
RemesTop:master
Open

Basic CMS fetching Playwright tests#153
RemesTop wants to merge 2 commits into
serveri:masterfrom
RemesTop:master

Conversation

@RemesTop

@RemesTop RemesTop commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

Tests for cms content fetching. Currently tests landing page and events page. Tests are not automatically run in github actions as they are a bit slow, but they can be run there manually.
kuva

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds opt-in Playwright integration tests that verify the app correctly fetches and renders live content from the Directus CMS. The tests are excluded from the default CI run (via --grep-invert @cms) and can only be triggered manually via GitHub Actions workflow_dispatch, or run locally with pnpm test:e2e:cms.

Changes:

  • New cms-live.spec.ts test file covering the Directus events API endpoint, the events page rendering, and the landing page's bilingual long description
  • Updated package.json and playwright.config.ts to add the test:e2e:cms script and exclude CMS tests from default runs
  • Updated README.md and .github/workflows/playwright.yml to document and support the opt-in CMS test execution

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
app/tests/cms-live.spec.ts New test file with three CMS integration tests: API health check, events page rendering, and landing page bilingual content
app/tests/README.md Documentation for the new opt-in CMS test command and environment variable configuration
app/playwright.config.ts Minor import style change (import osimport * as os)
app/package.json Added test:e2e:cms script; added --grep-invert @cms to default test scripts to exclude CMS tests
.github/workflows/playwright.yml Added cms-test job with workflow_dispatch inputs to optionally run live CMS tests

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

await page.goto('/yhdistys/tapahtumat', { waitUntil: 'networkidle' });
await expect(page.locator('h1.custom-page-title').first()).toBeVisible();

await expect(page.getByText(FALLBACK_EVENT_TITLE)).toHaveCount(0);

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback check for the events page is incorrect and will always trivially pass, regardless of whether the CMS data was loaded successfully.

The assertion checks that the text 'Serveri ry:n 35-vuotis vuosijuhlat' has zero occurrences on the page. However, looking at the fallback data in tapahtumat.vue (the error branch), the fallback event object uses the field name header, not fi_otsikko/en_otsikko. The EvetsCard component renders its title via content[$i18n.locale + '_title'], which maps to the fi_title/en_title props passed from item.fi_otsikko / item.en_otsikko. As a result, the header field in the fallback object is never rendered to the DOM — and the actual default text shown by EvetsCard when the prop is missing is 'Miksi serverit on niin kuumia?...'.

The assertion should instead check that the EvetsCard component's default fallback text is NOT present. For example, using page.getByText('Miksi serverit on niin kuumia?') or page.getByText('Why are the servers so hot?') (which are the actual EvetsCard component defaults defined in EvetsCard.vue).

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +112
const fiSnippet = getAnchorSnippet(fiLongDesc, 'Server');
const enSnippet = getAnchorSnippet(enLongDesc, 'Server');

await setLocaleCookie(page, 'fi');
await page.goto('/', { waitUntil: 'networkidle' });
await expect(page.locator('.rich-text').first()).toContainText(fiSnippet);
await expect(page.getByText('Miksi serverit on niin kuumia?')).toHaveCount(0);

await setLocaleCookie(page, 'en');
await page.goto('/en', { waitUntil: 'networkidle' });
await expect(page.locator('.rich-text').first()).toContainText(enSnippet);

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fi_long_desc and en_long_desc fields from Directus are stored as Markdown and rendered using vue-markdown-render (as seen in DescriptionText.vue). The test extracts a raw snippet from the Markdown string returned by the API and then checks if the rendered DOM .rich-text element contains that snippet.

If the word "Server" appears inside Markdown syntax in the CMS content (e.g., **Serveri**, _Serveri_, or [Serveri](url)), the raw snippet extracted by getAnchorSnippet would include the surrounding Markdown characters (asterisks, underscores, brackets), while the rendered DOM text would contain only the plain text. This would cause toContainText(fiSnippet) to fail.

To make this more robust, consider stripping Markdown syntax from the raw CMS content before extracting the snippet, or matching only a short word-boundary fragment like 'Server' directly without depending on the surrounding characters.

Suggested change
const fiSnippet = getAnchorSnippet(fiLongDesc, 'Server');
const enSnippet = getAnchorSnippet(enLongDesc, 'Server');
await setLocaleCookie(page, 'fi');
await page.goto('/', { waitUntil: 'networkidle' });
await expect(page.locator('.rich-text').first()).toContainText(fiSnippet);
await expect(page.getByText('Miksi serverit on niin kuumia?')).toHaveCount(0);
await setLocaleCookie(page, 'en');
await page.goto('/en', { waitUntil: 'networkidle' });
await expect(page.locator('.rich-text').first()).toContainText(enSnippet);
await setLocaleCookie(page, 'fi');
await page.goto('/', { waitUntil: 'networkidle' });
await expect(page.locator('.rich-text').first()).toContainText('Server');
await expect(page.getByText('Miksi serverit on niin kuumia?')).toHaveCount(0);
await setLocaleCookie(page, 'en');
await page.goto('/en', { waitUntil: 'networkidle' });
await expect(page.locator('.rich-text').first()).toContainText('Server');

Copilot uses AI. Check for mistakes.
import { test, expect } from '@playwright/test';

const CMS_BASE_URL = process.env.CMS_BASE_URL || process.env.NUXT_API_URL || 'https://api.serveriry.fi/';
const DIRECTUS_EVENTS_PATH = '/items/tapahtuma';

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DIRECTUS_EVENTS_PATH constant has a leading slash ('/items/tapahtuma'), but the landing page path is constructed without one ('items/LandingPage'). When calling new URL(path, base), a path starting with / is treated as an absolute path relative to the origin, while one without a leading slash is treated as relative to the base URL.

For standard Directus deployments at the domain root this works fine, but if CMS_BASE_URL ever includes a path prefix (e.g., https://example.com/api/), the events URL would be constructed incorrectly (https://example.com/items/tapahtuma) while the landing page URL would be correct (https://example.com/api/items/LandingPage). For consistency and to prevent subtle failures if the base URL changes, both path arguments should use the same style — preferably without a leading slash.

Suggested change
const DIRECTUS_EVENTS_PATH = '/items/tapahtuma';
const DIRECTUS_EVENTS_PATH = 'items/tapahtuma';

Copilot uses AI. Check for mistakes.
Comment thread app/tests/README.md
Comment on lines +57 to +59
```
$env:NUXT_API_URL='http://127.0.0.1:30001/'
$env:CMS_BASE_URL='http://127.0.0.1:30001'

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PowerShell example sets NUXT_API_URL with a trailing slash ('http://127.0.0.1:30001/') but CMS_BASE_URL without one ('http://127.0.0.1:30001'). In cms-live.spec.ts line 3, CMS_BASE_URL env var takes precedence over NUXT_API_URL, so when both are set as shown, only CMS_BASE_URL is used in the test.

However, NUXT_API_URL is used by the Nuxt app itself (to fetch from the CMS for the app's own API calls) and it requires a trailing slash for correct URL concatenation in the app (e.g., ${config.public['API_URL']}items/tapahtuma). The inconsistency in the example is harmless for the test but could confuse readers about whether trailing slashes matter. It would be clearer to document both variables with a trailing slash, or add a note explaining why they differ.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants