Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# AGENTS

## Entrypoint
- CLI entrypoint is `bin/fire` (executes `bin/fire.php`); it requires Composer autoload to exist, so run `composer install` before local runs.

## Config + discovery
- `fire.yml` and optional `fire.local.yml` are read from the project root (four directories above `vendor/fourkitchens/fire`); if missing, only `InitCommand.php` is registered and the CLI prompts to run `fire init`.
- Local env auto-detection sets `local_environment` based on `.lando.yml` or `.ddev/config.yaml` in the project root.

## Command sources
- Core commands live in `src/Robo/Plugin/Commands/*Command.php` and are discovered via Robo `CommandFileDiscovery`.
- Project-level custom commands are loaded from `fire/src/Commands` in the project root (path is computed by stripping `vendor/fourkitchens/` from the package path).

## Config template
- `fire init` uses the template at `assets/templates/fire.yml`; update it if you change required config fields.
79 changes: 78 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ Example:

Alias: `vlec`

- `vrt:playwright:init`: Configure Playwright VRT scaffolding using Automated Testing Kit.

Alias: `vpinit`

- `vrt:reference`: Takes new reference screeshots from the reference URL.

Alias: `vref`
Expand All @@ -210,11 +214,84 @@ Example:
**2. Full:** It replaces all existing code and allows you to write the command from scratch.

You can also create a new command, just choice the "Custom" option at the prompt when it ask you for for the command you want to overwrite, then respond to the questions, now a new command should have been created in the custom path, by default only a task is added to cleans the Drupal cache, but from this file, you can add your custom tasks.

- `platform:uli`: This command allows you to generate a one-time login URL for any environment hosted on Pantheon, Acquia, or Platform.sh.

Alias: `puli`

## Playwright VRT

FIRE supports Playwright-based visual regression testing as an alternative to the deprecated Backstop workflow.

### Initialize Playwright VRT

Set `ATK_HOME` to the Playwright test root before running the init command:

```
export ATK_HOME=./tests/playwright
fire vrt:playwright:init
```

The init command installs the required Drupal packages, runs the Automated Testing Kit Playwright scaffold, copies FIRE's Playwright templates, installs Node dependencies, installs `@fkbender/playwright-vrt-scripts`, installs Playwright browsers, and adds these npm scripts to `tests/playwright/package.json`:

```
"scripts": {
"vrt": "playwright-vrt",
"vrt:local": "playwright-vrt-local",
"vrt:ci": "playwright-vrt-ci"
}
```

If `NVM_DIR` is set and `tests/playwright/.nvmrc` exists, FIRE sources NVM and runs the npm commands using the Node version defined in `.nvmrc`.

### Environment Settings

During initialization, FIRE asks for:

- Baseline URL
- Baseline Terminus env
- Baseline Terminus site
- Candidate URL, usually the local URL

Those values are written to `tests/playwright/.env`:

```
BASELINE_URL="https://www.aft.org"
CANDIDATE_URL="http://aft-main.lndo.site"
BASELINE_TERMINUS_ENV=live
BASELINE_TERMINUS_SITE=aft-main
```

The `.env` file is added to `tests/playwright/.gitignore` by the init command.

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.

I think we need to add a part here where we describe how to edit the common pages.

### Run Playwright VRT

After initialization, run Playwright VRT with:

```
fire vrt:run --tool=playwright
```

or from the Playwright test root:

```
cd tests/playwright
npm run vrt
```

`fire vrt:reference --tool=playwright` also runs the Playwright VRT npm script from `tests/playwright`.

### Automatic Tool Detection

`vrt:run` and `vrt:reference` default to `--tool=auto`.

FIRE detects configured tools using these files:

- Backstop: `tests/backstop/backstop.json` or `tests/backstop/backstop-local.json`
- Playwright: `tests/playwright/package.json` or `tests/playwright/playwright.config.js`

If only one tool is configured, FIRE uses it automatically. If both Backstop and Playwright are configured in an interactive shell, FIRE asks which one to run. In non-interactive runs with both configured, FIRE defaults to Playwright.

## Configuration
Into your project root create a file called: `fire.yml` and iside of it speficify your global project settings.

Expand Down
3 changes: 3 additions & 0 deletions assets/templates/fire.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ remote_canonical_env: live

# (Optional setting), the system will automatically detected your local env, currently available: ddev, lando.
#local_environment : lando

# (Optional setting), default VRT tool to use. Options: backstop, playwright.
#vrt_tool: backstop
1 change: 1 addition & 0 deletions assets/templates/playwright/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.15.0
7 changes: 7 additions & 0 deletions assets/templates/playwright/css/screenshotGlobalStyle.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#toolbar-administration {
visibility: hidden;
}

.gin-secondary-toolbar__layout-container {
visibility: hidden;
}
12 changes: 12 additions & 0 deletions assets/templates/playwright/data/vrtCommonPages.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"name": "Home",
"path": "/",
"screenshotName": "home-page"
},
{
"name": "about-us",
"path": "/about-us",
"screenshotName": "about-us"
}
]
45 changes: 45 additions & 0 deletions assets/templates/playwright/playwright.atk.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Automated Testing Kit configuration.
*/
var currentEnv = process.env.TERMINUS_ENV || 'local';
var runOnPantheon = false;
if (currentEnv != 'local') {
runOnPantheon = true;
}

module.exports = {
operatingMode: "native",
drushCmd: "__DRUSH_CMD__",
articleAddUrl: 'node/add/article',
contactUsUrl: "form/contact",
logInUrl: "user/login",
logOutUrl: "user/logout",
imageAddUrl: 'media/add/image',
mediaDeleteUrl: 'media/{mid}/delete',
mediaEditUrl: 'media/{mid}/edit',
mediaList: 'admin/content/media',
menuAddUrl: 'admin/structure/menu/manage/main/add',
menuDeleteUrl: 'admin/structure/menu/item/{mid}/delete',
menuEditUrl: 'admin/structure/menu/item/{mid}/edit',
menuListUrl: 'admin/structure/menu/manage/main',
nodeDeleteUrl: 'node/{nid}/delete',
nodeEditUrl: 'node/{nid}/edit',
pageAddUrl: 'node/add/page',
registerUrl: "user/register",
resetPasswordUrl: "user/password",
termAddUrl: 'admin/structure/taxonomy/manage/terms/add',
termEditUrl: 'taxonomy/term/{tid}/edit',
termDeleteUrl: 'taxonomy/term/{tid}/delete',
termListUrl: 'admin/structure/taxonomy/manage/terms/overview',
termViewUrl: 'taxonomy/term/{tid}',
xmlSitemapUrl: 'admin/config/search/simplesitemap',
authDir: "tests/support",
dataDir: "tests/data",
supportDir: "tests/support",
testDir: "tests",
pantheon : {
isTarget: runOnPantheon,
site: "__PANTHEON_SITE__",
environment: currentEnv
}
}
72 changes: 72 additions & 0 deletions assets/templates/playwright/playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// @ts-check
const { defineConfig, devices } = require('@playwright/test');
import path from 'path'

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
require('dotenv').config();

const configuredBaseURL = (process.env.REMOTE_ENV_BASE_URL || '__DEFAULT_BASE_URL__')

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.

It looks like this file reads process.env.REMOTE_ENV_BASE_URL to set the base URL. But the .env file the command generates writes CANDIDATE_URL and BASELINE_URL Mismatch?

.trim()
.replace(/\/+$/, '')

/**
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
// Timeout.
timeout: 100000,
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? 'blob' : 'html',

expect: {
// timeout per assertion.
timeout: 100000,
toHaveScreenshot: {
stylePath: './css/screenshotGlobalStyle.css'
}
},

/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: `${configuredBaseURL}/`,

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on',
launchOptions: {
slowMo: 0
}
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
userAgent: 'my-site-playwright-ci/1.0'
},
},

// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },

// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
],
});
46 changes: 46 additions & 0 deletions assets/templates/playwright/tests/support/4k_utilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { devices } = require('@playwright/test')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We're mixing 'require' and 'export' in the same file. We should pick one standard.

// Helper function for lazy loading images.
const forceLoadLazyImages = async (page) => {
await page.evaluate(async () => {
document.querySelectorAll('[decoding="async"]').forEach((element) => {
element.decoding = 'sync'
})

const images = Array.from(document.querySelectorAll('img[loading="lazy"]'))

for (const image of images) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

FWIW, for loops can be slow sequence. Since this is JS we can fire these off in parallel and just await the work to be done.

image.loading = 'eager'
image.removeAttribute('loading')

try {
await image.decode()
} catch (error) {
// Ignore decode errors for lazy images.
}
}
})
}

// Returns devices profiles for VRT.
const vrtDeviceProfiles = (() => {
const desktopChromeProfile = { ...devices['Desktop Chrome'] }
const iPadProfile = { ...devices['iPad (gen 7)'] }
const iPhone12Profile = { ...devices['iPhone 12'] }

delete desktopChromeProfile.defaultBrowserType
delete iPadProfile.defaultBrowserType
delete iPhone12Profile.defaultBrowserType

return {
desktopChrome: desktopChromeProfile,
iPad: iPadProfile,
iPhone12: iPhone12Profile,
}
})()

const getVrtDeviceProfile = (key) => vrtDeviceProfiles[key]

export {
forceLoadLazyImages,
getVrtDeviceProfile,
}
59 changes: 59 additions & 0 deletions assets/templates/playwright/tests/vrt/common.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as aftUtilities from '../support/4k_utilities'
import commonPages from '../data/vrtCommonPages.json'

const { test, expect } = require('@playwright/test');

//test.describe.configure({ mode: 'serial' });

const runCommonVrtTest = async ({ page, path, screenshotName }) => {

await page.goto(path);
// Force-load lazy images.
await aftUtilities.forceLoadLazyImages(page);
await page.waitForLoadState('networkidle');

await page.addStyleTag({
content: `
* {
animation: none !important;
transition: none !important;
}
`
});

await expect(page).toHaveScreenshot(screenshotName, { fullPage: true });
}

const deviceProfiles = [
{
title: 'Desktop',
profile: 'desktopChrome',
screenshotSuffix: 'desktop',
},
{
title: 'iPad',
profile: 'iPad',
screenshotSuffix: 'ipad',
},
{
title: 'iPhone 12',
profile: 'iPhone12',
screenshotSuffix: 'iphone-12',
},
]

for (const device of deviceProfiles) {
test.describe(`Common VRT - ${device.title}`, () => {
test.use({ ...aftUtilities.getVrtDeviceProfile(device.profile) });

for (const commonPage of commonPages) {
test(`Common VRT - ${commonPage.name} - ${device.screenshotSuffix} @vrt`, async ({ page }) => {
await runCommonVrtTest({
page,
path: commonPage.path,
screenshotName: `${commonPage.screenshotName}-${device.screenshotSuffix}.png`,
});
});
}
});
}
Loading