From 444119ba1669261d949d288737799a5b5483807b Mon Sep 17 00:00:00 2001
From: Ilya Lyaukin <kzerby@gmail.com>
Date: Fri, 21 Mar 2025 08:04:18 -0600
Subject: [PATCH 1/2] Add prerequisites check (for cypress)

---
 cypress/support/atk_utilities.js | 39 ++++++++++++++++++++-----
 cypress/support/e2e.js           | 50 ++++++++++++++++++++++++++++++++
 data/atk_prerequisites.yml       |  7 +++++
 3 files changed, 89 insertions(+), 7 deletions(-)
 create mode 100644 cypress/support/e2e.js
 create mode 100644 data/atk_prerequisites.yml

diff --git a/cypress/support/atk_utilities.js b/cypress/support/atk_utilities.js
index b56113a..fe97687 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 0000000..3f03cc6
--- /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 0000000..ce7f181
--- /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'
+
-- 
GitLab


From db1dbe1acac414fa557104fe3a12e8fe2ff0089f Mon Sep 17 00:00:00 2001
From: Ilya Lyaukin <kzerby@gmail.com>
Date: Fri, 21 Mar 2025 10:25:19 -0600
Subject: [PATCH 2/2] Prerequisites check (for playwright)

---
 .../e2e/atk_caching/atk_caching.spec.js       |  6 +--
 .../e2e/atk_contact_us/atk_contact_us.spec.js |  6 +--
 playwright/e2e/atk_entity/atk_media.spec.js   |  6 +--
 playwright/e2e/atk_entity/atk_node.spec.js    |  6 +--
 .../e2e/atk_entity/atk_taxonomy.spec.js       |  6 +--
 playwright/e2e/atk_entity/atk_user.spec.js    |  6 +--
 playwright/e2e/atk_menu/atk_menu.spec.js      |  6 +--
 .../e2e/atk_page_error/atk_page_error.spec.js |  6 +--
 .../atk_register_login.spec.js                |  6 +--
 playwright/e2e/atk_search/atk_search.spec.js  | 30 ++++++-------
 .../e2e/atk_sitemap/atk_sitemap.spec.js       |  7 +--
 playwright/support/atk_commands.js            | 43 +++++++++++++++++--
 playwright/support/atk_utilities.js           | 29 ++++++++++++-
 13 files changed, 113 insertions(+), 50 deletions(-)

diff --git a/playwright/e2e/atk_caching/atk_caching.spec.js b/playwright/e2e/atk_caching/atk_caching.spec.js
index 6bd1cf8..8218696 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 cc33967..29b1f3d 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 23a7f8c..a0bfc18 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 bc849e5..e48bad1 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 3c7b914..c4c950e 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 ac7d40d..459e762 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 3dfba02..dbb5650 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 249a4c0..db84d41 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 ccba0b8..79ca890 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 7033c36..d4033b7 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 ac6f1db..4aee722 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 7786ba0..ee1e9e4 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 afe2bd2..a426c8d 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,
 }
-- 
GitLab