diff --git a/cypress/support/atk_utilities.js b/cypress/support/atk_utilities.js
index b56113af5706a13bb79d8b9bd4f14c9d032296b7..fe97687ea667da2e08fabbabfaa0073e71db50e1 100644
--- a/cypress/support/atk_utilities.js
+++ b/cypress/support/atk_utilities.js
@@ -1,14 +1,14 @@
 /// <reference types='Cypress' />
 
-import fs from 'fs';
-import YAML from 'yaml';
+import fs from 'fs'
+import YAML from 'yaml'
 
 /**
  * Return a string of random characters of specified length.
  *
  * @param {length}        int   Length of string to return.
  */
-function createRandomString (length) {
+function createRandomString(length) {
   let result = ''
   const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
   const charactersLength = characters.length
@@ -25,8 +25,8 @@ function createRandomString (length) {
  * @return {{userRoles: *[], userPassword: string, userEmail: string, userName: string}}
  */
 function createRandomUser() {
-  const name1 = createRandomString(6);
-  const name2 = createRandomString(6);
+  const name1 = createRandomString(6)
+  const name2 = createRandomString(6)
   return {
     userName: `${name1} ${name2}`,
     userEmail: `${name1.toLowerCase()}.${name2.toLowerCase()}@ethereal.email`,
@@ -42,8 +42,33 @@ function createRandomUser() {
  * @return {object}
  */
 function readYAML(filename) {
-  return cy.readFile(`cypress/data/${filename}`).then((text) => YAML.parse(text));
+  return cy.readFile(`cypress/data/${filename}`).then((text) => YAML.parse(text))
 }
 
+/**
+ * Get multi-level property from an object.
+ * E.g. if object is {"foo":{"bar":"buzz"}} and key is "foo.bar",
+ * "buzz" will be returned.
+ * If key at some level does not exist, null is returned.
+ *
+ * @param object {*} Initial object.
+ * @param key {string} Property path.
+ * @return {*}
+ */
+function getProperty(object, key) {
+  let result
+  result = object
+  for (const p of key.split('.')) {
+    if (result === undefined) {
+      return null
+    }
+    result = result[p]
+  }
+
+  if (result === undefined) {
+    return null
+  }
+  return result
+}
 
-export { createRandomString, createRandomUser, readYAML }
+export { createRandomString, createRandomUser, readYAML, getProperty }
diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f03cc6d9f8753038b859bb115b4983b477dd764
--- /dev/null
+++ b/cypress/support/e2e.js
@@ -0,0 +1,50 @@
+// ***********************************************************
+// This example support/e2e.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './atk_commands'
+import { getProperty, readYAML } from './atk_utilities'
+
+// Do environment check before all tests.
+before(() => {
+  readYAML('atk_prerequisites.yml').then((prerequisites) => {
+    for (const prerequisite of prerequisites) {
+      if ('command' in prerequisite) {
+        cy.execDrush(prerequisite.command).then((output) => {
+          if (prerequisite.json) {
+            const outputJson = JSON.parse(output)
+            // Each property of prerequisite.json is a condition.
+            for (const [key, condition] of Object.entries(prerequisite.json)) {
+              const value = getProperty(outputJson, key)
+              if (typeof condition !== 'object') {
+                throw new Error('Condition must be {"eq":...} or ...')
+              }
+              for (const [conditionType, conditionValue] of Object.entries(condition)) {
+                switch (conditionType) {
+                  case 'eq':
+                    expect(value).to.eq(conditionValue)
+                    break
+                  // ...
+                  default:
+                    throw new Error(`Condition ${conditionType} is not implemented`)
+                }
+              }
+            }
+          }
+        })
+      }
+    }
+  })
+})
diff --git a/data/atk_prerequisites.yml b/data/atk_prerequisites.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ce7f181152ca3b0cfff8a3b782642ace5d88f8b4
--- /dev/null
+++ b/data/atk_prerequisites.yml
@@ -0,0 +1,7 @@
+- command: 'pm:list --format=json'
+  json:
+    automated_testing_kit.status:
+      eq: 'Enabled'
+    qa_accounts.status:
+      eq: 'Enabled'
+
diff --git a/playwright/e2e/atk_caching/atk_caching.spec.js b/playwright/e2e/atk_caching/atk_caching.spec.js
index 6bd1cf8364cc6a9296999364c1652459ec3a53a2..8218696e81d327eba3edfc4a6c25493f31cc441a 100644
--- a/playwright/e2e/atk_caching/atk_caching.spec.js
+++ b/playwright/e2e/atk_caching/atk_caching.spec.js
@@ -5,15 +5,15 @@
  *
  */
 
+// Set up Playwright.
+import { expect, test } from '@playwright/test'
+
 /** ESLint directives */
 /* eslint-disable import/first */
 
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test, expect } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL
diff --git a/playwright/e2e/atk_contact_us/atk_contact_us.spec.js b/playwright/e2e/atk_contact_us/atk_contact_us.spec.js
index cc339673d887e8cd871fa71d12f6a1c44446f44b..29b1f3d9c431b7be5dcd5c7435971dedd4843d17 100644
--- a/playwright/e2e/atk_contact_us/atk_contact_us.spec.js
+++ b/playwright/e2e/atk_contact_us/atk_contact_us.spec.js
@@ -8,12 +8,12 @@
 /** ESLint directives */
 /* eslint-disable import/first */
 
+// Set up Playwright.
+import { expect, test } from '@playwright/test'
+
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test, expect } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL
diff --git a/playwright/e2e/atk_entity/atk_media.spec.js b/playwright/e2e/atk_entity/atk_media.spec.js
index 23a7f8cf5a72543b6f99569f2baffb4f34243e90..a0bfc181a15b7537cbf0b5b9ea95db46d570e228 100644
--- a/playwright/e2e/atk_entity/atk_media.spec.js
+++ b/playwright/e2e/atk_entity/atk_media.spec.js
@@ -8,12 +8,12 @@
 /** ESLint directives */
 /* eslint-disable import/first */
 
+// Set up Playwright.
+import { expect, test } from '@playwright/test'
+
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test, expect } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL
diff --git a/playwright/e2e/atk_entity/atk_node.spec.js b/playwright/e2e/atk_entity/atk_node.spec.js
index bc849e5ddd068c5927f51fe69130f492d3fa7c54..e48bad1a9b211966f3d464c73c78132d443f44c4 100644
--- a/playwright/e2e/atk_entity/atk_node.spec.js
+++ b/playwright/e2e/atk_entity/atk_node.spec.js
@@ -8,12 +8,12 @@
 /** ESLint directives */
 /* eslint-disable import/first */
 
+// Set up Playwright.
+import { expect, test } from '@playwright/test'
+
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test, expect } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL
diff --git a/playwright/e2e/atk_entity/atk_taxonomy.spec.js b/playwright/e2e/atk_entity/atk_taxonomy.spec.js
index 3c7b914f9f0feafa68317e5702bb280c9a44820e..c4c950e19a7e24f835c6afc3126199c76c6e9f2a 100644
--- a/playwright/e2e/atk_entity/atk_taxonomy.spec.js
+++ b/playwright/e2e/atk_entity/atk_taxonomy.spec.js
@@ -8,12 +8,12 @@
 /** ESLint directives */
 /* eslint-disable import/first */
 
+// Set up Playwright.
+import { expect, test } from '@playwright/test'
+
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test, expect } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL
diff --git a/playwright/e2e/atk_entity/atk_user.spec.js b/playwright/e2e/atk_entity/atk_user.spec.js
index ac7d40d1634bb8ef20574cf8ab247252145c823f..459e762619f8d7e04ea57300cac0b16cfbc0d97f 100644
--- a/playwright/e2e/atk_entity/atk_user.spec.js
+++ b/playwright/e2e/atk_entity/atk_user.spec.js
@@ -8,12 +8,12 @@
 /** ESLint directives */
 /* eslint-disable import/first */
 
+// Set up Playwright.
+import { test } from '@playwright/test'
+
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL // eslint-disable-line no-unused-vars
diff --git a/playwright/e2e/atk_menu/atk_menu.spec.js b/playwright/e2e/atk_menu/atk_menu.spec.js
index 3dfba02156267a7c6b629f839305f6869944d8c6..dbb5650b26a1395f7922e5c0274136f8855c3906 100644
--- a/playwright/e2e/atk_menu/atk_menu.spec.js
+++ b/playwright/e2e/atk_menu/atk_menu.spec.js
@@ -7,12 +7,12 @@
 /** ESLint directives */
 /* eslint-disable import/first */
 
+// Set up Playwright.
+import { test } from '@playwright/test'
+
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL
diff --git a/playwright/e2e/atk_page_error/atk_page_error.spec.js b/playwright/e2e/atk_page_error/atk_page_error.spec.js
index 249a4c01eb99203b7015ff2b38e1cf4309ca7664..db84d4101434abde4eab64edb07d70ed139f2f9e 100644
--- a/playwright/e2e/atk_page_error/atk_page_error.spec.js
+++ b/playwright/e2e/atk_page_error/atk_page_error.spec.js
@@ -8,12 +8,12 @@
 /** ESLint directives */
 /* eslint-disable import/first */
 
+// Set up Playwright.
+import { expect, test } from '@playwright/test'
+
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test, expect } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL
diff --git a/playwright/e2e/atk_register_login/atk_register_login.spec.js b/playwright/e2e/atk_register_login/atk_register_login.spec.js
index ccba0b866633be009a0b0a5748d30909890c87b7..79ca89042095d6cddd97dcbac3ae6a336506b668 100644
--- a/playwright/e2e/atk_register_login/atk_register_login.spec.js
+++ b/playwright/e2e/atk_register_login/atk_register_login.spec.js
@@ -7,12 +7,12 @@
 /** ESLint directives */
 /* eslint-disable import/first */
 
+// Set up Playwright.
+import { test } from '@playwright/test'
+
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL
diff --git a/playwright/e2e/atk_search/atk_search.spec.js b/playwright/e2e/atk_search/atk_search.spec.js
index 7033c366eaede7badb77fdc1043ca6b4e6c9a8b1..d4033b7ade512ddce28adf8d3d8173cd5bdb127a 100644
--- a/playwright/e2e/atk_search/atk_search.spec.js
+++ b/playwright/e2e/atk_search/atk_search.spec.js
@@ -5,15 +5,15 @@
  *
  */
 
+// Set up Playwright.
+import { expect, test } from '@playwright/test'
+
 /** ESLint directives */
 /* eslint-disable import/first */
 
 import * as atkCommands from '../support/atk_commands'
 import * as atkUtilities from '../support/atk_utilities'
 
-// Set up Playwright.
-const { test, expect } = require('@playwright/test')
-
 import playwrightConfig from '../../playwright.config'
 
 const baseUrl = playwrightConfig.use.baseURL
@@ -36,14 +36,14 @@ test.describe('Search tests.', () => {
 
     await page.goto(baseUrl)
 
-    const searchForm = page.getByLabel('Search Form');
-    const isSearchFormVisible = await searchForm.isVisible();
+    const searchForm = page.getByLabel('Search Form')
+    const isSearchFormVisible = await searchForm.isVisible()
     if (!isSearchFormVisible) {
-      await page.getByLabel('Main Menu').click();
+      await page.getByLabel('Main Menu').click()
     }
 
     for (const item of searchData.simple) {
-      await openSearchForm(page);
+      await openSearchForm(page)
       const keyInput = page.getByRole('searchbox', { name: 'Search' })
       await keyInput.fill(item.keyword)
       await keyInput.press('Enter')
@@ -106,13 +106,13 @@ test.describe('Search tests.', () => {
 
     await page.goto(baseUrl)
 
-    const searchForm = page.getByLabel('Search Form');
-    const isSearchFormVisible = await searchForm.isVisible();
+    const searchForm = page.getByLabel('Search Form')
+    const isSearchFormVisible = await searchForm.isVisible()
     if (!isSearchFormVisible) {
-      await page.getByLabel('Main Menu').click();
+      await page.getByLabel('Main Menu').click()
     }
 
-    await openSearchForm(page);
+    await openSearchForm(page)
     const searchInput = page.getByRole('searchbox', { name: 'Search' })
     await expect(searchInput).toHaveAttribute('placeholder', 'Search by keyword or phrase.')
   })
@@ -138,12 +138,12 @@ test.describe('Search tests.', () => {
     // Handle "responsive design". If "Search form" isn't visible,
     // have to click main menu button first.
 
-    let searchForm = page.getByLabel('Search Form');
-    await searchForm.waitFor();
+    const searchForm = page.getByLabel('Search Form')
+    await searchForm.waitFor()
     if (!(await searchForm.isVisible())) {
-      await page.getByLabel('Main Menu').click();
+      await page.getByLabel('Main Menu').click()
     }
-    await searchForm.click();
+    await searchForm.click()
   }
 
   async function checkResult(page, item) {
diff --git a/playwright/e2e/atk_sitemap/atk_sitemap.spec.js b/playwright/e2e/atk_sitemap/atk_sitemap.spec.js
index ac6f1db34852ff93788fb53c865ab3734fbf036d..4aee722fab2c476e5796e6fd7df99c5f56b68413 100644
--- a/playwright/e2e/atk_sitemap/atk_sitemap.spec.js
+++ b/playwright/e2e/atk_sitemap/atk_sitemap.spec.js
@@ -11,11 +11,12 @@
 import { XMLParser } from 'fast-xml-parser'
 import axios from 'axios'
 import https from 'https'
-import * as atkUtilities from '../support/atk_utilities' // eslint-disable-line no-unused-vars
-import * as atkCommands from '../support/atk_commands'
 
 // Set up Playwright.
-const { test, expect } = require('@playwright/test')
+import { expect, test } from '@playwright/test'
+
+import * as atkUtilities from '../support/atk_utilities' // eslint-disable-line no-unused-vars
+import * as atkCommands from '../support/atk_commands'
 
 import playwrightConfig from '../../playwright.config'
 
diff --git a/playwright/support/atk_commands.js b/playwright/support/atk_commands.js
index 7786ba0a89bb4ee44f826268563d93ce4c1d4055..ee1e9e4e46704aa8cab6ed6cacf9202da0815e1c 100644
--- a/playwright/support/atk_commands.js
+++ b/playwright/support/atk_commands.js
@@ -15,6 +15,7 @@ import playwrightConfig from '../../playwright.config'
 
 // Fetch the Automated Testing Kit config, which is in the project root.
 import atkConfig from '../../playwright.atk.config'
+import { getProperty, readYAML } from './atk_utilities'
 
 const baseUrl = playwrightConfig.use.baseURL
 
@@ -63,12 +64,12 @@ function createUserWithUserObject(user, roles = [], args = [], options = []) {
   // Attempt to add the roles.
   // Role(s) may come from the user object or the function arguments.
   if (user.hasOwnProperty('userRoles')) {
-    user.userRoles.forEach(function (role) {
+    user.userRoles.forEach((role) => {
       roles.push(role)
     })
   }
 
-  roles.forEach(function (role) {
+  roles.forEach((role) => {
     cmd = `user:role:add '${role}' '${user.userName}'`
     execDrush(cmd)
 
@@ -413,8 +414,8 @@ async function inputTextIntoCKEditor(page, text, instanceNumber = 0) {
 
         // Attempt to get the CKEditor instance.
         const editorInstance = targetEditorElement.ckeditorInstance
-          || Object.values(CKEDITOR.instances)[editorIndex]
-          || Object.values(ClassicEditor.instances)[editorIndex]
+              || Object.values(CKEDITOR.instances)[editorIndex]
+              || Object.values(ClassicEditor.instances)[editorIndex]
 
         if (editorInstance) {
           // Set the data in the editor.
@@ -518,6 +519,40 @@ function setDrupalConfiguration(objectName, key, value) {
   execDrush(cmd)
 }
 
+// Check global prerequisites.
+// (Once per test run.)
+const prerequisites = readYAML('atk_prerequisites.yml')
+const { prerequisitesOk } = globalThis
+if (prerequisitesOk === undefined) {
+  globalThis.prerequisitesOk = false
+  for (const prerequisite of prerequisites) {
+    if ('command' in prerequisite) {
+      const output = execDrush(prerequisite.command)
+      if (prerequisite.json) {
+        const outputJson = JSON.parse(output)
+        // Each property of prerequisite.json is a condition.
+        for (const [key, condition] of Object.entries(prerequisite.json)) {
+          const value = getProperty(outputJson, key)
+          if (typeof condition !== 'object') {
+            throw new Error('Condition must be {"eq":...} or ...')
+          }
+          for (const [conditionType, conditionValue] of Object.entries(condition)) {
+            switch (conditionType) {
+              case 'eq':
+                expect(value).toEqual(conditionValue)
+                break
+                // ...
+              default:
+                throw new Error(`Condition ${conditionType} is not implemented`)
+            }
+          }
+        }
+      }
+    }
+  }
+  globalThis.prerequisitesOk = true
+}
+
 export {
   createUserWithUserObject,
   deleteCurrentNodeViaUi,
diff --git a/playwright/support/atk_utilities.js b/playwright/support/atk_utilities.js
index afe2bd28a8afdea39a7b2466afa9f67b5b715427..a426c8d8bc88c8d44f7d0ad7286c4b4121b2f6fe 100644
--- a/playwright/support/atk_utilities.js
+++ b/playwright/support/atk_utilities.js
@@ -45,8 +45,35 @@ function readYAML(filename) {
   return YAML.parse(file)
 }
 
+/**
+ * Get multi-level property from an object.
+ * E.g. if object is {"foo":{"bar":"buzz"}} and key is "foo.bar",
+ * "buzz" will be returned.
+ * If key at some level does not exist, null is returned.
+ *
+ * @param object {*} Initial object.
+ * @param key {string} Property path.
+ * @return {*}
+ */
+function getProperty(object, key) {
+  let result
+  result = object
+  for (const p of key.split('.')) {
+    if (result === undefined) {
+      return null
+    }
+    result = result[p]
+  }
+
+  if (result === undefined) {
+    return null
+  }
+  return result
+}
+
 export {
   createRandomString,
   createRandomUser,
-  readYAML
+  readYAML,
+  getProperty,
 }