Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,27 @@
|
||||
module.exports.assertion = function (expected) {
|
||||
this.message = `Testing if "${expected}" deprecation error has been triggered`;
|
||||
this.expected = expected;
|
||||
this.pass = (deprecationMessages) => deprecationMessages.includes(expected);
|
||||
this.value = (result) => {
|
||||
const sessionStorageEntries = JSON.parse(result.value);
|
||||
const deprecationMessages =
|
||||
sessionStorageEntries !== null
|
||||
? sessionStorageEntries.filter((message) =>
|
||||
message.includes('[Deprecation]'),
|
||||
)
|
||||
: [];
|
||||
|
||||
return deprecationMessages.map((message) =>
|
||||
message.replace('[Deprecation] ', ''),
|
||||
);
|
||||
};
|
||||
this.command = (callback) =>
|
||||
// eslint-disable-next-line prefer-arrow-callback
|
||||
this.api.execute(
|
||||
function () {
|
||||
return window.sessionStorage.getItem('js_testing_log_test.warnings');
|
||||
},
|
||||
[],
|
||||
callback,
|
||||
);
|
||||
};
|
||||
22
web/core/tests/Drupal/Nightwatch/Assertions/elementCount.js
Normal file
22
web/core/tests/Drupal/Nightwatch/Assertions/elementCount.js
Normal file
@ -0,0 +1,22 @@
|
||||
module.exports.assertion = function (selector, count) {
|
||||
this.message = `Testing if element <${selector}> has count: ${count}`;
|
||||
this.expected = count;
|
||||
this.pass = function (val) {
|
||||
return val === this.expected;
|
||||
};
|
||||
this.value = function (res) {
|
||||
return res.value;
|
||||
};
|
||||
this.command = function (cb) {
|
||||
const self = this;
|
||||
return this.api.execute(
|
||||
function (selector) {
|
||||
return document.querySelectorAll(selector).length;
|
||||
},
|
||||
[selector],
|
||||
function (res) {
|
||||
cb.call(self, res);
|
||||
},
|
||||
);
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,27 @@
|
||||
module.exports.assertion = function () {
|
||||
this.message = 'Ensuring no deprecation errors have been triggered';
|
||||
this.expected = '';
|
||||
this.pass = (deprecationMessages) => deprecationMessages.length === 0;
|
||||
this.value = (result) => {
|
||||
const sessionStorageEntries = JSON.parse(result.value);
|
||||
const deprecationMessages =
|
||||
sessionStorageEntries !== null
|
||||
? sessionStorageEntries.filter((message) =>
|
||||
message.includes('[Deprecation]'),
|
||||
)
|
||||
: [];
|
||||
|
||||
return deprecationMessages.map((message) =>
|
||||
message.replace('[Deprecation] ', ''),
|
||||
);
|
||||
};
|
||||
this.command = (callback) =>
|
||||
// eslint-disable-next-line prefer-arrow-callback
|
||||
this.api.execute(
|
||||
function () {
|
||||
return window.sessionStorage.getItem('js_testing_log_test.warnings');
|
||||
},
|
||||
[],
|
||||
callback,
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Creates role with given permissions.
|
||||
*
|
||||
* @param {object} settings
|
||||
* Settings object
|
||||
* @param {array} settings.permissions
|
||||
* The list of roles granted for the user.
|
||||
* @param {string} [settings.name=null]
|
||||
* The role name.
|
||||
* @param {function} callback
|
||||
* A callback which will be called, when creating the role is finished.
|
||||
* @return {object}
|
||||
* The drupalCreateRole command.
|
||||
*/
|
||||
exports.command = function drupalCreateRole(
|
||||
{ permissions, name = null },
|
||||
callback,
|
||||
) {
|
||||
const self = this;
|
||||
const roleName = name || Math.random().toString(36).substring(2, 15);
|
||||
|
||||
let machineName;
|
||||
this.drupalLoginAsAdmin(async () => {
|
||||
this.drupalRelativeURL('/admin/people/roles/add');
|
||||
this.setValue('input[name="label"]', roleName);
|
||||
|
||||
this.execute(() => {
|
||||
jQuery('input[name="label"]').trigger('formUpdated');
|
||||
});
|
||||
// Wait for the machine name to appear so that it can be used later to
|
||||
// select the permissions from the permission page.
|
||||
this.expect
|
||||
.element('.user-role-form .machine-name-value')
|
||||
.to.be.visible.before(2000);
|
||||
|
||||
machineName = await this.getText('.user-role-form .machine-name-value');
|
||||
this.submitForm('#user-role-form').assert.textContains(
|
||||
'[data-drupal-messages]',
|
||||
`Role ${roleName} has been added.`,
|
||||
);
|
||||
|
||||
this.drupalRelativeURL('/admin/people/permissions').waitForElementVisible(
|
||||
'table.permissions',
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
permissions.map(async (permission) =>
|
||||
this.click(`input[name="${machineName}[${permission}]"]`),
|
||||
),
|
||||
);
|
||||
|
||||
this.submitForm('#user-admin-permissions').assert.textContains(
|
||||
'[data-drupal-messages]',
|
||||
'The changes have been saved.',
|
||||
);
|
||||
}).perform(() => {
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(self, machineName);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Logs into Drupal as the given user.
|
||||
*
|
||||
* @param {object} settings
|
||||
* Settings object
|
||||
* @param {string} settings.name
|
||||
* The user name.
|
||||
* @param {string} settings.password
|
||||
* The user password.
|
||||
* @param {array} [settings.permissions=[]]
|
||||
* The list of permissions granted for the user.
|
||||
* @param {function} callback
|
||||
* A callback which will be called when creating the user is finished.
|
||||
* @return {object}
|
||||
* The drupalCreateUser command.
|
||||
*/
|
||||
exports.command = function drupalCreateUser(
|
||||
{ name, password, permissions = [] },
|
||||
callback,
|
||||
) {
|
||||
const self = this;
|
||||
|
||||
// Define the name here because the callback from drupalCreateRole can be
|
||||
// undefined in some cases.
|
||||
const roleName = Math.random()
|
||||
.toString(36)
|
||||
.replace(/[^\w\d]/g, '')
|
||||
.substring(2, 15);
|
||||
this.perform((client, done) => {
|
||||
if (permissions.length) {
|
||||
this.drupalCreateRole({ permissions, name: roleName }, done);
|
||||
}
|
||||
}).drupalLoginAsAdmin(async () => {
|
||||
this.drupalRelativeURL('/admin/people/create')
|
||||
.setValue('input[name="name"]', name)
|
||||
.setValue('input[name="pass[pass1]"]', password)
|
||||
.setValue('input[name="pass[pass2]"]', password)
|
||||
.perform((client, done) => {
|
||||
if (permissions.length) {
|
||||
client.click(`input[name="roles[${roleName}]`, () => {
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
})
|
||||
.submitForm('#user-register-form')
|
||||
.assert.textContains(
|
||||
'[data-drupal-messages]',
|
||||
'Created a new user account',
|
||||
`User "${name}" was created successfully.`,
|
||||
);
|
||||
});
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(self);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Enable a given theme.
|
||||
*
|
||||
* @param themeMachineName
|
||||
* The theme machine name to enable
|
||||
* @param adminTheme
|
||||
* If true, install the theme as the admin theme instead of default.
|
||||
* @return {object}
|
||||
* The drupalEnableTheme command.
|
||||
*/
|
||||
exports.command = function drupalEnableTheme(
|
||||
themeMachineName,
|
||||
adminTheme = false,
|
||||
) {
|
||||
this.drupalLoginAsAdmin(() => {
|
||||
const path = adminTheme
|
||||
? '/admin/theme/install_admin/'
|
||||
: '/admin/theme/install_default/';
|
||||
this.drupalRelativeURL(`${path}${themeMachineName}`).waitForElementPresent(
|
||||
'#theme-installed',
|
||||
10000,
|
||||
);
|
||||
});
|
||||
return this;
|
||||
};
|
||||
69
web/core/tests/Drupal/Nightwatch/Commands/drupalInstall.js
Normal file
69
web/core/tests/Drupal/Nightwatch/Commands/drupalInstall.js
Normal file
@ -0,0 +1,69 @@
|
||||
const { execSync } = require('node:child_process');
|
||||
const { URL } = require('node:url');
|
||||
const { commandAsWebserver } = require('../globals');
|
||||
|
||||
/**
|
||||
* Installs a Drupal test site.
|
||||
*
|
||||
* @param {object} [settings={}]
|
||||
* Settings object
|
||||
* @param {string} [settings.setupFile='']
|
||||
* Setup file used by TestSiteApplicationTest
|
||||
* @param {string} [settings.installProfile='']
|
||||
* The install profile to use.
|
||||
* @param {string} [settings.langcode='']
|
||||
* The language to install the site in.
|
||||
* @param {function} callback
|
||||
* A callback which will be called, when the installation is finished.
|
||||
* @return {object}
|
||||
* The 'browser' object.
|
||||
*/
|
||||
exports.command = function drupalInstall(
|
||||
{ setupFile = '', installProfile = 'nightwatch_testing', langcode = '' } = {},
|
||||
callback,
|
||||
) {
|
||||
const self = this;
|
||||
|
||||
// Ensure no session cookie exists anymore; they won't work on this newly installed Drupal site anyway.
|
||||
this.cookies.deleteAll();
|
||||
|
||||
try {
|
||||
setupFile = setupFile ? `--setup-file "${setupFile}"` : '';
|
||||
installProfile = `--install-profile "${installProfile}"`;
|
||||
const langcodeOption = langcode ? `--langcode "${langcode}"` : '';
|
||||
const dbOption =
|
||||
process.env.DRUPAL_TEST_DB_URL.length > 0
|
||||
? `--db-url "${process.env.DRUPAL_TEST_DB_URL}"`
|
||||
: '';
|
||||
const install = execSync(
|
||||
commandAsWebserver(
|
||||
`php ./scripts/test-site.php install ${setupFile} ${installProfile} ${langcodeOption} --base-url ${process.env.DRUPAL_TEST_BASE_URL} ${dbOption} --json`,
|
||||
),
|
||||
);
|
||||
const installData = JSON.parse(install.toString());
|
||||
this.globals.drupalDbPrefix = installData.db_prefix;
|
||||
this.globals.drupalSitePath = installData.site_path;
|
||||
const url = new URL(process.env.DRUPAL_TEST_BASE_URL);
|
||||
this.url(process.env.DRUPAL_TEST_BASE_URL).setCookie({
|
||||
name: 'SIMPLETEST_USER_AGENT',
|
||||
// Colons need to be URL encoded to be valid.
|
||||
value: encodeURIComponent(installData.user_agent),
|
||||
path: url.pathname,
|
||||
});
|
||||
// Set the HTTP_USER_AGENT environment variable to detect the test
|
||||
// environment in the command line.
|
||||
process.env.HTTP_USER_AGENT = installData.user_agent;
|
||||
} catch (error) {
|
||||
this.assert.fail(error);
|
||||
}
|
||||
|
||||
// Nightwatch doesn't like it when no actions are added in a command file.
|
||||
// https://github.com/nightwatchjs/nightwatch/issues/1792
|
||||
this.pause(1);
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(self);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Install the given module.
|
||||
*
|
||||
* @param {string} module
|
||||
* The module machine name to enable.
|
||||
* @param {boolean} force
|
||||
* Force to install dependencies if applicable.
|
||||
* @param {function} callback
|
||||
* A callback which will be called, when the module has been enabled.
|
||||
* @return {object}
|
||||
* The drupalInstallModule command.
|
||||
*/
|
||||
exports.command = function drupalInstallModule(module, force, callback) {
|
||||
const self = this;
|
||||
this.drupalLoginAsAdmin(() => {
|
||||
this.drupalRelativeURL('/admin/modules')
|
||||
// Filter module list to ensure that collapsable <details> elements are expanded.
|
||||
.updateValue(
|
||||
'form.system-modules [data-drupal-selector="edit-text"]',
|
||||
module,
|
||||
)
|
||||
.waitForElementVisible(
|
||||
`form.system-modules [name="modules[${module}][enable]"]`,
|
||||
10000,
|
||||
)
|
||||
.click(`form.system-modules [name="modules[${module}][enable]"]`)
|
||||
.submitForm('form.system-modules');
|
||||
if (force) {
|
||||
// Click `Continue` if applicable.
|
||||
this.waitForElementPresent(
|
||||
'#system-modules-confirm-form, #system-modules-non-stable-confirm-form',
|
||||
10000,
|
||||
false,
|
||||
() => self.click('input[value=Continue]'),
|
||||
);
|
||||
}
|
||||
// Wait for the checkbox for the module to be disabled as a sign that the
|
||||
// module has been enabled.
|
||||
this.waitForElementPresent(
|
||||
`form.system-modules [name="modules[${module}][enable]"]:disabled`,
|
||||
10000,
|
||||
);
|
||||
}).perform(() => {
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(self);
|
||||
}
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
27
web/core/tests/Drupal/Nightwatch/Commands/drupalLogAndEnd.js
Normal file
27
web/core/tests/Drupal/Nightwatch/Commands/drupalLogAndEnd.js
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Ends the browser session and logs the console log if there were any errors.
|
||||
* See globals.js.
|
||||
*
|
||||
* @param {Object}
|
||||
* (optional) Settings object
|
||||
* @param onlyOnError
|
||||
* (optional) Only writes out the console log file if the test failed.
|
||||
* @param {function} callback
|
||||
* A callback which will be called.
|
||||
* @return {object}
|
||||
* The 'browser' object.
|
||||
*/
|
||||
exports.command = function drupalLogAndEnd({ onlyOnError = true }, callback) {
|
||||
const self = this;
|
||||
this.drupalLogConsole = true;
|
||||
this.drupalLogConsoleOnlyOnError = onlyOnError;
|
||||
|
||||
// Nightwatch doesn't like it when no actions are added in a command file.
|
||||
// https://github.com/nightwatchjs/nightwatch/issues/1792
|
||||
this.pause(1);
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(self);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
36
web/core/tests/Drupal/Nightwatch/Commands/drupalLogin.js
Normal file
36
web/core/tests/Drupal/Nightwatch/Commands/drupalLogin.js
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Logs into Drupal as the given user.
|
||||
*
|
||||
* @param {string} name
|
||||
* The user name.
|
||||
* @param {string} password
|
||||
* The user password.
|
||||
* @return {object}
|
||||
* The drupalUserIsLoggedIn command.
|
||||
*/
|
||||
exports.command = function drupalLogin({ name, password }) {
|
||||
this.drupalUserIsLoggedIn((sessionExists) => {
|
||||
// Log the current user out if necessary.
|
||||
if (sessionExists) {
|
||||
this.drupalLogout();
|
||||
}
|
||||
// Log in with the given credentials.
|
||||
this.drupalRelativeURL('/user/login')
|
||||
.setValue('input[name="name"]', name)
|
||||
.setValue('input[name="pass"]', password)
|
||||
.submitForm('#user-login-form');
|
||||
// MongoDB needs a moment, because it is using a replica set and the
|
||||
// members of the replica set need to synchronize.
|
||||
this.pause(50);
|
||||
// Assert that a user is logged in.
|
||||
this.drupalUserIsLoggedIn((sessionExists) => {
|
||||
this.assert.equal(
|
||||
sessionExists,
|
||||
true,
|
||||
`The user "${name}" was logged in.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -0,0 +1,40 @@
|
||||
const { execSync } = require('node:child_process');
|
||||
const { commandAsWebserver } = require('../globals');
|
||||
|
||||
/**
|
||||
* Logs in as the admin user.
|
||||
*
|
||||
* @param {function} callback
|
||||
* A callback which will allow running commands as an administrator.
|
||||
* @return {object}
|
||||
* The drupalLoginAsAdmin command.
|
||||
*/
|
||||
exports.command = function drupalLoginAsAdmin(callback) {
|
||||
const self = this;
|
||||
this.drupalUserIsLoggedIn((sessionExists) => {
|
||||
if (sessionExists) {
|
||||
this.drupalLogout();
|
||||
}
|
||||
const userLink = execSync(
|
||||
commandAsWebserver(
|
||||
`php ./scripts/test-site.php user-login 1 --site-path ${this.globals.drupalSitePath}`,
|
||||
),
|
||||
);
|
||||
|
||||
this.drupalRelativeURL(userLink.toString());
|
||||
|
||||
this.drupalUserIsLoggedIn((sessionExists) => {
|
||||
if (!sessionExists) {
|
||||
throw new Error('Logging in as an admin user failed.');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(self);
|
||||
}
|
||||
|
||||
this.drupalLogout({ silent: true });
|
||||
|
||||
return this;
|
||||
};
|
||||
39
web/core/tests/Drupal/Nightwatch/Commands/drupalLogout.js
Normal file
39
web/core/tests/Drupal/Nightwatch/Commands/drupalLogout.js
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Logs out from a Drupal site.
|
||||
*
|
||||
* @param {object} [settings={}]
|
||||
* The settings object.
|
||||
* @param {boolean} [settings.silent=false]
|
||||
* If the command should be run silently.
|
||||
* @param {function} callback
|
||||
* A callback which will be called, when the logout is finished.
|
||||
* @return {object}
|
||||
* The drupalLogout command.
|
||||
*/
|
||||
exports.command = function drupalLogout({ silent = false } = {}, callback) {
|
||||
const self = this;
|
||||
|
||||
this.drupalRelativeURL('/user/logout/confirm').submitForm(
|
||||
'#user-logout-confirm',
|
||||
);
|
||||
|
||||
// MongoDB needs a moment, because it is using a replica set and the
|
||||
// members of the replica set need to synchronize.
|
||||
this.pause(50);
|
||||
|
||||
this.drupalUserIsLoggedIn((sessionExists) => {
|
||||
if (silent) {
|
||||
if (sessionExists) {
|
||||
throw new Error('Logging out failed.');
|
||||
}
|
||||
} else {
|
||||
this.assert.equal(sessionExists, false, 'The user was logged out.');
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(self);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Concatenate a DRUPAL_TEST_BASE_URL variable and a pathname.
|
||||
*
|
||||
* This provides a custom command, .relativeURL()
|
||||
*
|
||||
* @param {string} pathname
|
||||
* The relative path to append to DRUPAL_TEST_BASE_URL
|
||||
* @param {function} callback
|
||||
* A callback which will be called.
|
||||
* @return {object}
|
||||
* The 'browser' object.
|
||||
*/
|
||||
exports.command = function drupalRelativeURL(pathname, callback) {
|
||||
const self = this;
|
||||
this.url(`${process.env.DRUPAL_TEST_BASE_URL}${pathname}`);
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(self);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
46
web/core/tests/Drupal/Nightwatch/Commands/drupalUninstall.js
Normal file
46
web/core/tests/Drupal/Nightwatch/Commands/drupalUninstall.js
Normal file
@ -0,0 +1,46 @@
|
||||
const { execSync } = require('node:child_process');
|
||||
const { commandAsWebserver } = require('../globals');
|
||||
|
||||
/**
|
||||
* Uninstalls a test Drupal site.
|
||||
*
|
||||
* @param {function} callback
|
||||
* A callback which will be called, when the uninstallation is finished.
|
||||
* @return {object}
|
||||
* The 'browser' object.
|
||||
*/
|
||||
exports.command = function drupalUninstall(callback) {
|
||||
const self = this;
|
||||
const prefix = this.globals.drupalDbPrefix;
|
||||
|
||||
// Check for any existing errors, because running this will cause Nightwatch to hang.
|
||||
if (!this.currentTest.results.errors && !this.currentTest.results.failed) {
|
||||
const dbOption =
|
||||
process.env.DRUPAL_TEST_DB_URL.length > 0
|
||||
? `--db-url "${process.env.DRUPAL_TEST_DB_URL}"`
|
||||
: '';
|
||||
try {
|
||||
if (!prefix || !prefix.length) {
|
||||
throw new Error(
|
||||
'Missing database prefix parameter, unable to uninstall Drupal (the initial install was probably unsuccessful).',
|
||||
);
|
||||
}
|
||||
execSync(
|
||||
commandAsWebserver(
|
||||
`php ./scripts/test-site.php tear-down ${prefix} ${dbOption}`,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
this.assert.fail(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Nightwatch doesn't like it when no actions are added in a command file.
|
||||
// https://github.com/nightwatchjs/nightwatch/issues/1792
|
||||
this.pause(1);
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(self);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Checks if a user is logged in.
|
||||
*
|
||||
* @param {function} callback
|
||||
* A callback which will be called, when the login status has been checked.
|
||||
* @return {object}
|
||||
* The drupalUserIsLoggedIn command.
|
||||
*/
|
||||
exports.command = function drupalUserIsLoggedIn(callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this.cookies.getAll((cookies) => {
|
||||
const sessionExists = cookies.value.some((cookie) =>
|
||||
cookie.name.match(/^S?SESS/),
|
||||
);
|
||||
|
||||
callback.call(this, sessionExists);
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -0,0 +1,101 @@
|
||||
const mainContent = '.region-content';
|
||||
const mainMessagesContainer =
|
||||
'[data-drupal-messages] > .messages-list__wrapper';
|
||||
const secondaryMessagesContainer = '[data-drupal-messages-other]';
|
||||
|
||||
const mainButtons = {
|
||||
addStatus: '#add--status',
|
||||
removeStatus: '#remove--status',
|
||||
addError: '#add--error',
|
||||
removeError: '#remove--error',
|
||||
addWarning: '#add--warning',
|
||||
removeWarning: '#remove--warning',
|
||||
clearAll: '#clear-all',
|
||||
};
|
||||
|
||||
const secondaryButtons = {
|
||||
addStatus: '[id="add-[data-drupal-messages-other]-status"]',
|
||||
removeStatus: '[id="remove-[data-drupal-messages-other]-status"]',
|
||||
addError: '[id="add-[data-drupal-messages-other]-error"]',
|
||||
removeError: '[id="remove-[data-drupal-messages-other]-error"]',
|
||||
addWarning: '[id="add-[data-drupal-messages-other]-warning"]',
|
||||
removeWarning: '[id="remove-[data-drupal-messages-other]-warning"]',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'claro'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall()
|
||||
.drupalInstallModule('js_message_test')
|
||||
.drupalEnableTheme('claro');
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Verify default placement of javascript-created messages': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/js_message_test_link_with_system_messages')
|
||||
.waitForElementVisible(mainContent)
|
||||
.assert.elementPresent(mainMessagesContainer)
|
||||
|
||||
// We should load 3 messages on page load from \Drupal::messenger()
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 3)
|
||||
|
||||
// We should have one message of each type
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--status`, 1)
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 1)
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--error`, 1)
|
||||
|
||||
// Trigger new messages via javascript
|
||||
.click(mainButtons.addStatus)
|
||||
.click(mainButtons.addWarning)
|
||||
.click(mainButtons.addError)
|
||||
|
||||
// We should have 6 total messages
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 6)
|
||||
|
||||
// We should have 2 messages of each type
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--status`, 2)
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 2)
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--error`, 2);
|
||||
},
|
||||
|
||||
'Verify customized placement of javascript-created messages': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/js_message_test_link_with_system_messages')
|
||||
.waitForElementVisible(mainContent)
|
||||
.assert.elementPresent(secondaryMessagesContainer)
|
||||
|
||||
// We should load 3 messages on page load from \Drupal::messenger()
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages-list__item`,
|
||||
0,
|
||||
)
|
||||
|
||||
// Trigger new messages via javascript
|
||||
.click(secondaryButtons.addStatus)
|
||||
.click(secondaryButtons.addWarning)
|
||||
.click(secondaryButtons.addError)
|
||||
|
||||
// We should have 6 total messages
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages-list__item`,
|
||||
3,
|
||||
)
|
||||
|
||||
// We should have 2 messages of each type
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages--status`,
|
||||
1,
|
||||
)
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages--warning`,
|
||||
1,
|
||||
)
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages--error`,
|
||||
1,
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
// This test is a duplicate of oliveroPrimaryTabsTest.js tagged for claro
|
||||
const primaryTabsWrapper = '[data-drupal-nav-tabs]';
|
||||
const activeTab = '.tabs__tab.is-active';
|
||||
const inactiveTab = '.tabs__tab:not(.is-active)';
|
||||
const mobileToggle = `${activeTab} .tabs__trigger`;
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'claro'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteClaroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
})
|
||||
.drupalCreateUser({
|
||||
name: 'user',
|
||||
password: '123',
|
||||
permissions: ['administer nodes'],
|
||||
})
|
||||
.drupalLogin({ name: 'user', password: '123' });
|
||||
browser.window.setSize(1600, 800);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Verify desktop primary tab display': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/node/1')
|
||||
.waitForElementVisible(primaryTabsWrapper)
|
||||
.assert.visible(activeTab)
|
||||
.assert.visible(inactiveTab)
|
||||
.assert.not.visible(mobileToggle);
|
||||
},
|
||||
'Verify mobile tab display and click functionality': (browser) => {
|
||||
browser.window
|
||||
.setSize(699, 800)
|
||||
.drupalRelativeURL('/node/1')
|
||||
.waitForElementVisible(primaryTabsWrapper)
|
||||
.assert.visible(activeTab)
|
||||
.assert.not.visible(inactiveTab)
|
||||
.assert.visible(mobileToggle)
|
||||
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'false')
|
||||
.click(mobileToggle)
|
||||
.waitForElementVisible(inactiveTab)
|
||||
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'true')
|
||||
.click(mobileToggle)
|
||||
.waitForElementNotVisible(inactiveTab)
|
||||
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'false');
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,134 @@
|
||||
const selectors = {
|
||||
schemePicker: '[data-drupal-selector="edit-color-scheme"]',
|
||||
primaryColor: {
|
||||
text: 'input[type="text"][name="base_primary_color"]',
|
||||
color: 'input[type="color"][name="base_primary_color_visual"]',
|
||||
},
|
||||
submit: '[data-drupal-selector="edit-submit"]',
|
||||
siteHeader: '.site-header__initial',
|
||||
};
|
||||
|
||||
const colorSchemes = {
|
||||
default: {
|
||||
base_primary_color: '#1b9ae4',
|
||||
},
|
||||
firehouse: {
|
||||
base_primary_color: '#a30f0f',
|
||||
},
|
||||
ice: {
|
||||
base_primary_color: '#57919e',
|
||||
},
|
||||
plum: {
|
||||
base_primary_color: '#7a4587',
|
||||
},
|
||||
slate: {
|
||||
base_primary_color: '#47625b',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
})
|
||||
// Create user that can search.
|
||||
.drupalCreateUser({
|
||||
name: 'user',
|
||||
password: '123',
|
||||
permissions: ['administer themes', 'view the administration theme'],
|
||||
})
|
||||
.drupalLogin({ name: 'user', password: '123' });
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Olivero Settings - color schemes update individual values': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/admin/appearance/settings/olivero')
|
||||
.waitForElementVisible(selectors.schemePicker)
|
||||
.click(`${selectors.schemePicker} option[value="firehouse"]`)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.text,
|
||||
colorSchemes.firehouse.base_primary_color,
|
||||
)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.color,
|
||||
colorSchemes.firehouse.base_primary_color,
|
||||
)
|
||||
.click(`${selectors.schemePicker} option[value="ice"]`)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.text,
|
||||
colorSchemes.ice.base_primary_color,
|
||||
)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.color,
|
||||
colorSchemes.ice.base_primary_color,
|
||||
)
|
||||
.click(`${selectors.schemePicker} option[value="plum"]`)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.text,
|
||||
colorSchemes.plum.base_primary_color,
|
||||
)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.color,
|
||||
colorSchemes.plum.base_primary_color,
|
||||
)
|
||||
.click(`${selectors.schemePicker} option[value="slate"]`)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.text,
|
||||
colorSchemes.slate.base_primary_color,
|
||||
)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.color,
|
||||
colorSchemes.slate.base_primary_color,
|
||||
)
|
||||
.click(`${selectors.schemePicker} option[value="default"]`)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.text,
|
||||
colorSchemes.default.base_primary_color,
|
||||
)
|
||||
.assert.valueEquals(
|
||||
selectors.primaryColor.color,
|
||||
colorSchemes.default.base_primary_color,
|
||||
);
|
||||
},
|
||||
'Olivero Settings - color inputs stay synchronized': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/admin/appearance/settings/olivero')
|
||||
.waitForElementVisible(selectors.primaryColor.text)
|
||||
.waitForElementVisible(selectors.primaryColor.color)
|
||||
.updateValue(selectors.primaryColor.text, '#ff0000')
|
||||
.assert.valueEquals(selectors.primaryColor.color, '#ff0000')
|
||||
.updateValue(selectors.primaryColor.text, '#00ff00')
|
||||
.assert.valueEquals(selectors.primaryColor.color, '#00ff00')
|
||||
.updateValue(selectors.primaryColor.text, '#0000ff')
|
||||
.assert.valueEquals(selectors.primaryColor.color, '#0000ff');
|
||||
},
|
||||
'Olivero Settings - color selections impact olivero theme': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/admin/appearance/settings/olivero')
|
||||
.waitForElementVisible(selectors.primaryColor.color)
|
||||
.updateValue(selectors.primaryColor.text, '#ff0000') // hsl(0, 100%, 50%)
|
||||
.click(selectors.submit)
|
||||
.waitForElementVisible(selectors.primaryColor.color)
|
||||
.drupalRelativeURL('/')
|
||||
.waitForElementVisible(selectors.siteHeader)
|
||||
.expect.element(selectors.siteHeader)
|
||||
.to.have.css('backgroundColor', 'rgb(255, 0, 0)');
|
||||
|
||||
browser
|
||||
.drupalRelativeURL('/admin/appearance/settings/olivero')
|
||||
.waitForElementVisible(selectors.primaryColor.color)
|
||||
.updateValue(selectors.primaryColor.text, '#7a4587') // hsl(0, 100%, 50%)
|
||||
.click(selectors.submit)
|
||||
.waitForElementVisible(selectors.primaryColor.color)
|
||||
.drupalRelativeURL('/')
|
||||
.waitForElementVisible(selectors.siteHeader)
|
||||
.expect.element(selectors.siteHeader)
|
||||
.to.have.css('backgroundColor', 'rgb(122, 69, 135)');
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,41 @@
|
||||
const commentTitleSelector = 'h2.comments__title';
|
||||
const commentCountSelector = 'h2.comments__title .comments__count';
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
})
|
||||
.drupalCreateUser({
|
||||
name: 'user',
|
||||
password: '123',
|
||||
permissions: [
|
||||
'access comments',
|
||||
'post comments',
|
||||
'skip comment approval',
|
||||
],
|
||||
})
|
||||
.drupalLogin({ name: 'user', password: '123' });
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Article without comments should not display count': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/node/1')
|
||||
.assert.textContains('body', 'Article without comments')
|
||||
.assert.not.elementPresent(commentCountSelector);
|
||||
},
|
||||
'Article with comments should display count': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/node/2')
|
||||
.assert.textContains('body', 'Article with comments')
|
||||
.assert.elementPresent(commentTitleSelector)
|
||||
.assert.elementPresent(commentCountSelector)
|
||||
.assert.textContains(commentCountSelector, '2');
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,98 @@
|
||||
const headerNavSelector = '#header-nav';
|
||||
const linkSubMenuId = 'primary-menu-item-1';
|
||||
const buttonSubMenuId = 'primary-menu-item-12';
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
})
|
||||
// Login and change max-nesting depth so we can verify that menu levels
|
||||
// greater than 2 do not break the site.
|
||||
.drupalLoginAsAdmin(() => {
|
||||
browser
|
||||
.drupalRelativeURL('/admin/structure/block/manage/olivero_main_menu')
|
||||
.waitForElementVisible('[data-drupal-selector="edit-settings-depth"]')
|
||||
.setValue('[data-drupal-selector="edit-settings-depth"]', 'Unlimited')
|
||||
.click('[data-drupal-selector="edit-actions-submit"]')
|
||||
.waitForElementVisible('body');
|
||||
});
|
||||
browser.window.setSize(1600, 800);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Verify Olivero desktop menu click functionality': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/node')
|
||||
.waitForElementVisible(headerNavSelector)
|
||||
.assert.not.visible(`#${linkSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${linkSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'false',
|
||||
)
|
||||
.click(`[aria-controls="${linkSubMenuId}"]`)
|
||||
.assert.visible(`#${linkSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${linkSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'true',
|
||||
)
|
||||
// Verify tertiary menu item exists.
|
||||
.assert.visible('#primary-menu-item-11 .primary-nav__menu-link--level-3')
|
||||
// Test interactions for route:<button> menu links.
|
||||
.assert.not.visible(`#${buttonSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${buttonSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'false',
|
||||
)
|
||||
.click(`[aria-controls="${buttonSubMenuId}"]`)
|
||||
.assert.visible(`#${buttonSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${buttonSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'true',
|
||||
);
|
||||
},
|
||||
'Verify Olivero desktop menu hover functionality': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/node')
|
||||
.waitForElementVisible(headerNavSelector)
|
||||
.assert.visible(headerNavSelector)
|
||||
.assert.not.visible(`#${linkSubMenuId}`)
|
||||
.moveToElement(`[aria-controls="${linkSubMenuId}"]`, 1, 1)
|
||||
.assert.visible(`#${linkSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${linkSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'true',
|
||||
)
|
||||
.assert.not.visible(`#${buttonSubMenuId}`)
|
||||
.moveToElement(`[aria-controls="${buttonSubMenuId}"]`, 1, 1)
|
||||
.assert.visible(`#${buttonSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${buttonSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'true',
|
||||
);
|
||||
},
|
||||
'Verify desktop menu converts to mobile if it gets too long': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/node')
|
||||
.waitForElementVisible('body')
|
||||
.assert.not.elementPresent('body.is-always-mobile-nav')
|
||||
.setWindowSize(1220, 800)
|
||||
.execute(() => {
|
||||
// Directly modify the width of the site branding name so that it causes
|
||||
// the primary navigation to be too long, and switch into mobile mode.
|
||||
document.querySelector('.site-branding__name').style.width = '350px';
|
||||
}, [])
|
||||
.assert.elementPresent('body.is-always-mobile-nav');
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,101 @@
|
||||
const mainContent = '#block-olivero-content';
|
||||
const mainMessagesContainer = '[data-drupal-messages] > .messages__wrapper';
|
||||
const secondaryMessagesContainer = '[data-drupal-messages-other]';
|
||||
|
||||
const mainButtons = {
|
||||
addStatus: '#add--status',
|
||||
removeStatus: '#remove--status',
|
||||
addError: '#add--error',
|
||||
removeError: '#remove--error',
|
||||
addWarning: '#add--warning',
|
||||
removeWarning: '#remove--warning',
|
||||
clearAll: '#clear-all',
|
||||
};
|
||||
|
||||
const secondaryButtons = {
|
||||
addStatus: '[id="add-[data-drupal-messages-other]-status"]',
|
||||
removeStatus: '[id="remove-[data-drupal-messages-other]-status"]',
|
||||
addError: '[id="add-[data-drupal-messages-other]-error"]',
|
||||
removeError: '[id="remove-[data-drupal-messages-other]-error"]',
|
||||
addWarning: '[id="add-[data-drupal-messages-other]-warning"]',
|
||||
removeWarning: '[id="remove-[data-drupal-messages-other]-warning"]',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
});
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Verify default placement of javascript-created messages': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/js_message_test_link_with_system_messages')
|
||||
.waitForElementVisible(mainContent)
|
||||
.assert.elementPresent(mainMessagesContainer)
|
||||
|
||||
// We should load 3 messages on page load from \Drupal::messenger()
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 3)
|
||||
|
||||
// We should have one message of each type
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--status`, 1)
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 1)
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--error`, 1)
|
||||
|
||||
// Trigger new messages via javascript
|
||||
.click(mainButtons.addStatus)
|
||||
.click(mainButtons.addWarning)
|
||||
.click(mainButtons.addError)
|
||||
|
||||
// We should have 6 total messages
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages-list__item`, 6)
|
||||
|
||||
// We should have 2 messages of each type
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--status`, 2)
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--warning`, 2)
|
||||
.assert.elementCount(`${mainMessagesContainer} > .messages--error`, 2);
|
||||
},
|
||||
|
||||
'Verify customized placement of javascript-created messages': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/js_message_test_link_with_system_messages')
|
||||
.waitForElementVisible(mainContent)
|
||||
.assert.elementPresent(secondaryMessagesContainer)
|
||||
|
||||
// We should load 3 messages on page load from \Drupal::messenger()
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages-list__item`,
|
||||
0,
|
||||
)
|
||||
|
||||
// Trigger new messages via javascript
|
||||
.click(secondaryButtons.addStatus)
|
||||
.click(secondaryButtons.addWarning)
|
||||
.click(secondaryButtons.addError)
|
||||
|
||||
// We should have 6 total messages
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages-list__item`,
|
||||
3,
|
||||
)
|
||||
|
||||
// We should have 2 messages of each type
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages--status`,
|
||||
1,
|
||||
)
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages--warning`,
|
||||
1,
|
||||
)
|
||||
.assert.elementCount(
|
||||
`${secondaryMessagesContainer} > .messages--error`,
|
||||
1,
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,66 @@
|
||||
const preloadFontPaths = [
|
||||
'core/themes/olivero/fonts/metropolis/Metropolis-Regular.woff2',
|
||||
'core/themes/olivero/fonts/metropolis/Metropolis-SemiBold.woff2',
|
||||
'core/themes/olivero/fonts/metropolis/Metropolis-Bold.woff2',
|
||||
'core/themes/olivero/fonts/lora/lora-v14-latin-regular.woff2',
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
});
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Verify font loading': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/')
|
||||
.waitForElementVisible('body')
|
||||
// Check that <link rel="preload"> tags properly reference font.
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (preloadFontPaths) {
|
||||
const basePath = drupalSettings.path.baseUrl;
|
||||
let selectorsExist = true;
|
||||
preloadFontPaths.forEach((path) => {
|
||||
if (!document.head.querySelector(`[href="${basePath + path}"]`)) {
|
||||
selectorsExist = false;
|
||||
}
|
||||
});
|
||||
|
||||
return selectorsExist;
|
||||
},
|
||||
[preloadFontPaths],
|
||||
(result) => {
|
||||
browser.assert.ok(
|
||||
result.value,
|
||||
'Check that <link rel="preload"> tags properly reference font.',
|
||||
);
|
||||
},
|
||||
)
|
||||
// Check that the CSS @font-face declaration has loaded the font.
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function () {
|
||||
document.fonts.load('16px metropolis');
|
||||
document.fonts.load('16px Lora');
|
||||
return (
|
||||
document.fonts.check('16px metropolis') &&
|
||||
document.fonts.check('16px Lora')
|
||||
);
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.ok(
|
||||
result.value,
|
||||
'Check that the CSS @font-face declaration has loaded the font.',
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,200 @@
|
||||
const mobileNavButtonSelector = 'button.mobile-nav-button';
|
||||
const headerNavSelector = '#header-nav';
|
||||
const linkSubMenuId = 'primary-menu-item-1';
|
||||
const buttonSubMenuId = 'primary-menu-item-12';
|
||||
|
||||
/**
|
||||
* Sends arbitrary number of tab keys, and then checks that the last focused
|
||||
* element is within the given parent selector.
|
||||
*
|
||||
* @param {object} browser - Nightwatch Browser object
|
||||
* @param {string} parentSelector - Selector to which to test focused element against.
|
||||
* @param {number} tabCount - Amount of tab presses to send to browser
|
||||
* @param {boolean} [tabBackwards] - Hold down the SHIFT key when sending tabs
|
||||
*/
|
||||
const focusTrapCheck = (browser, parentSelector, tabCount, tabBackwards) => {
|
||||
if (tabBackwards === true) {
|
||||
browser.perform(function () {
|
||||
return this.actions().keyDown(browser.Keys.SHIFT);
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < tabCount; i++) {
|
||||
browser.perform(function () {
|
||||
return this.actions().sendKeys(browser.Keys.TAB).pause(50);
|
||||
});
|
||||
}
|
||||
browser
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (parentSelector) {
|
||||
// Verify focused element is still within the focus trap.
|
||||
return document.activeElement.matches(parentSelector);
|
||||
},
|
||||
[parentSelector],
|
||||
(result) => {
|
||||
browser.assert.ok(result.value);
|
||||
},
|
||||
)
|
||||
// Release SHIFT key.
|
||||
.perform(function () {
|
||||
return this.actions().keyUp(browser.Keys.SHIFT);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
})
|
||||
.setWindowSize(1000, 800);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Verify mobile menu and submenu functionality': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/')
|
||||
.assert.not.visible(headerNavSelector)
|
||||
.click(mobileNavButtonSelector)
|
||||
.waitForElementVisible(headerNavSelector)
|
||||
// Test interactions for normal <a> menu links.
|
||||
.assert.not.visible(`#${linkSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${linkSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'false',
|
||||
)
|
||||
.waitForElementVisible(`[aria-controls="${linkSubMenuId}"]`)
|
||||
.click(`[aria-controls="${linkSubMenuId}"]`)
|
||||
.waitForElementVisible(`#${linkSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${linkSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'true',
|
||||
)
|
||||
// Test interactions for route:<button> menu links.
|
||||
.assert.not.visible(`#${buttonSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${buttonSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'false',
|
||||
)
|
||||
.click(`[aria-controls="${buttonSubMenuId}"]`)
|
||||
.assert.visible(`#${buttonSubMenuId}`)
|
||||
.assert.attributeEquals(
|
||||
`[aria-controls="${buttonSubMenuId}"]`,
|
||||
'aria-expanded',
|
||||
'true',
|
||||
);
|
||||
},
|
||||
'Verify mobile menu focus trap': (browser) => {
|
||||
browser.drupalRelativeURL('/').click(mobileNavButtonSelector);
|
||||
focusTrapCheck(
|
||||
browser,
|
||||
`${headerNavSelector} *, ${mobileNavButtonSelector}`,
|
||||
17,
|
||||
);
|
||||
focusTrapCheck(
|
||||
browser,
|
||||
`${headerNavSelector} *, ${mobileNavButtonSelector}`,
|
||||
19,
|
||||
true,
|
||||
);
|
||||
},
|
||||
'Verify parent <button> focus on ESC in narrow navigation': (browser) => {
|
||||
browser
|
||||
// Verify functionality on regular link's button.
|
||||
.drupalRelativeURL('/node')
|
||||
.waitForElementVisible('body')
|
||||
.click(mobileNavButtonSelector)
|
||||
.waitForElementVisible(headerNavSelector)
|
||||
.waitForElementVisible(`[aria-controls="${linkSubMenuId}"]`)
|
||||
.click(`[aria-controls="${linkSubMenuId}"]`)
|
||||
.waitForElementVisible(`#${linkSubMenuId}`)
|
||||
.perform(function () {
|
||||
return this.actions().sendKeys(browser.Keys.TAB);
|
||||
})
|
||||
.pause(50)
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (linkSubMenuId) {
|
||||
return document.activeElement.matches(`#${linkSubMenuId} *`);
|
||||
},
|
||||
[linkSubMenuId],
|
||||
(result) => {
|
||||
browser.assert.ok(result.value);
|
||||
},
|
||||
)
|
||||
.perform(function () {
|
||||
return this.actions().sendKeys(browser.Keys.ESCAPE);
|
||||
})
|
||||
.pause(50)
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (linkSubMenuId) {
|
||||
return document.activeElement.matches(
|
||||
`[aria-controls="${linkSubMenuId}"]`,
|
||||
);
|
||||
},
|
||||
[linkSubMenuId],
|
||||
(result) => {
|
||||
browser.assert.ok(result.value);
|
||||
},
|
||||
)
|
||||
// Verify functionality on route:<button> button.
|
||||
.click(`[aria-controls="${buttonSubMenuId}"]`)
|
||||
.waitForElementVisible(`#${buttonSubMenuId}`)
|
||||
.perform(function () {
|
||||
return this.actions().sendKeys(browser.Keys.TAB);
|
||||
})
|
||||
.pause(50)
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (buttonSubMenuId) {
|
||||
return document.activeElement.matches(`#${buttonSubMenuId} *`);
|
||||
},
|
||||
[buttonSubMenuId],
|
||||
(result) => {
|
||||
browser.assert.ok(result.value);
|
||||
},
|
||||
)
|
||||
.perform(function () {
|
||||
return this.actions().sendKeys(browser.Keys.ESCAPE);
|
||||
})
|
||||
.pause(50)
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (buttonSubMenuId) {
|
||||
return document.activeElement.matches(
|
||||
`[aria-controls="${buttonSubMenuId}"]`,
|
||||
);
|
||||
},
|
||||
[buttonSubMenuId],
|
||||
(result) => {
|
||||
browser.assert.ok(result.value);
|
||||
},
|
||||
);
|
||||
},
|
||||
'Verify clicks on hashes close mobile menu': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/node')
|
||||
.waitForElementVisible('body')
|
||||
.click(mobileNavButtonSelector)
|
||||
.waitForElementVisible(headerNavSelector)
|
||||
.click('[href="#footer"]')
|
||||
.waitForElementNotVisible(headerNavSelector);
|
||||
},
|
||||
'Verify mobile menu works when Big Pipe when authenticated': (browser) => {
|
||||
browser.drupalInstallModule('big_pipe').drupalLoginAsAdmin(() => {
|
||||
browser
|
||||
.drupalRelativeURL('/')
|
||||
.assert.not.visible(headerNavSelector)
|
||||
.click(mobileNavButtonSelector)
|
||||
.waitForElementVisible(headerNavSelector);
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,41 @@
|
||||
const tableSelector = '#edit-field-multiple-value-form-field-wrapper table';
|
||||
const tableHeaderSelector = '#edit-field-multiple-value-form-field-wrapper th';
|
||||
const headerSelector = '#edit-field-multiple-value-form-field-wrapper h4';
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
})
|
||||
.drupalCreateUser({
|
||||
name: 'user',
|
||||
password: '123',
|
||||
permissions: ['access site-wide contact form'],
|
||||
})
|
||||
.drupalLogin({ name: 'user', password: '123' });
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'correct classes added to table and header': (browser) => {
|
||||
browser
|
||||
.setWindowSize(1400, 800)
|
||||
.drupalRelativeURL('/contact/olivero_test_contact_form')
|
||||
.waitForElementVisible(tableSelector, 1000)
|
||||
.assert.hasClass(tableSelector, [
|
||||
'tabledrag-disabled',
|
||||
'js-tabledrag-disabled',
|
||||
])
|
||||
.assert.hasClass(tableHeaderSelector, 'is-disabled')
|
||||
.assert.hasClass(headerSelector, [
|
||||
'form-item__label',
|
||||
'form-item__label--multiple-value-form',
|
||||
'js-form-required',
|
||||
'form-required',
|
||||
]);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,112 @@
|
||||
const checkboxSelector = '#edit-form-checkboxes-title-attribute';
|
||||
|
||||
const inputTypes = [
|
||||
{
|
||||
selector: '#edit-form-textfield-test-title-and-required',
|
||||
type: 'text',
|
||||
api: 'textfield',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-email-title-no-xss',
|
||||
type: 'email',
|
||||
api: 'email',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-tel-title-no-xss',
|
||||
type: 'tel',
|
||||
api: 'tel',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-number-title-no-xss',
|
||||
type: 'number',
|
||||
api: 'number',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-search-title-no-xss',
|
||||
type: 'search',
|
||||
api: 'search',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-password-title-no-xss',
|
||||
type: 'password',
|
||||
api: 'password',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-date-title-no-xss',
|
||||
type: 'date',
|
||||
api: 'date',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-datetime-title-no-xss-time',
|
||||
type: 'time',
|
||||
api: 'date',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-file-title-no-xss',
|
||||
type: 'file',
|
||||
api: 'file',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-color-title-no-xss',
|
||||
type: 'color',
|
||||
api: 'color',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-url-title-no-xss',
|
||||
type: 'url',
|
||||
api: 'url',
|
||||
},
|
||||
// TODO - Cover datetime-local, month, week - no test form input examples are easily available
|
||||
];
|
||||
|
||||
const booleanInputTypes = [
|
||||
{
|
||||
selector: '#edit-form-checkboxes-test-first-checkbox',
|
||||
type: 'checkbox',
|
||||
},
|
||||
{
|
||||
selector: '#edit-form-radios-title-attribute-first-radio',
|
||||
type: 'radio',
|
||||
},
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
});
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Confirm that title attribute exists if set to display': (browser) => {
|
||||
browser
|
||||
.setWindowSize(1400, 800)
|
||||
.drupalRelativeURL('/form_test/form-labels')
|
||||
.waitForElementVisible(checkboxSelector, 1000)
|
||||
.assert.attributeEquals(
|
||||
checkboxSelector,
|
||||
'title',
|
||||
'Checkboxes test (Required)',
|
||||
);
|
||||
},
|
||||
'Check form element classes by type': (browser) => {
|
||||
browser.drupalRelativeURL('/form_test/form-labels');
|
||||
inputTypes.forEach((inputType) => {
|
||||
browser.assert.hasClass(inputType.selector, [
|
||||
'form-element',
|
||||
`form-element--type-${inputType.type}`,
|
||||
`form-element--api-${inputType.api}`,
|
||||
]);
|
||||
});
|
||||
booleanInputTypes.forEach((booleanInputType) => {
|
||||
browser.assert.hasClass(booleanInputType.selector, [
|
||||
'form-boolean',
|
||||
`form-boolean--type-${booleanInputType.type}`,
|
||||
]);
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
const primaryTabsWrapper = '[data-drupal-nav-primary-tabs]';
|
||||
const activeTab = '.tabs__tab.is-active';
|
||||
const inactiveTab = '.tabs__tab:not(.is-active)';
|
||||
const mobileToggle = `${activeTab} .tabs__trigger`;
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
})
|
||||
.drupalCreateUser({
|
||||
name: 'user',
|
||||
password: '123',
|
||||
permissions: ['administer nodes'],
|
||||
})
|
||||
.drupalLogin({ name: 'user', password: '123' });
|
||||
browser.window.setSize(1600, 800);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Verify desktop primary tab display': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/node/1')
|
||||
.waitForElementVisible(primaryTabsWrapper)
|
||||
.assert.visible(activeTab)
|
||||
.assert.visible(inactiveTab)
|
||||
.assert.not.visible(mobileToggle);
|
||||
},
|
||||
'Verify mobile tab display and click functionality': (browser) => {
|
||||
browser
|
||||
.setWindowSize(699, 800)
|
||||
.drupalRelativeURL('/node/1')
|
||||
.waitForElementVisible(primaryTabsWrapper)
|
||||
.assert.visible(activeTab)
|
||||
.assert.not.visible(inactiveTab)
|
||||
.assert.visible(mobileToggle)
|
||||
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'false')
|
||||
.click(mobileToggle)
|
||||
.waitForElementVisible(inactiveTab)
|
||||
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'true')
|
||||
.click(mobileToggle)
|
||||
.waitForElementNotVisible(inactiveTab)
|
||||
.assert.attributeEquals(mobileToggle, 'aria-expanded', 'false');
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,57 @@
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
})
|
||||
.drupalLoginAsAdmin(() => {
|
||||
browser
|
||||
.drupalRelativeURL('/admin/structure/block')
|
||||
|
||||
// Disable narrow search form block.
|
||||
.click(
|
||||
'[data-drupal-selector="edit-blocks-olivero-search-form-narrow-operations"] .dropbutton-toggle button',
|
||||
)
|
||||
.click('[href*="olivero_search_form_narrow/disable"]')
|
||||
|
||||
// Disable main menu block.
|
||||
.click(
|
||||
'[data-drupal-selector="edit-blocks-olivero-main-menu-operations"] .dropbutton-toggle button',
|
||||
)
|
||||
.click('[href*="olivero_main_menu/disable"]')
|
||||
|
||||
// Disable wide search form block.
|
||||
.click(
|
||||
'[data-drupal-selector="edit-blocks-olivero-search-form-wide-operations"] .dropbutton-toggle button',
|
||||
)
|
||||
.click('[href*="olivero_search_form_wide/disable"]')
|
||||
|
||||
// Disable user account menu block.
|
||||
.click(
|
||||
'[data-drupal-selector="edit-blocks-olivero-account-menu-operations"] .dropbutton-toggle button',
|
||||
)
|
||||
.click('[href*="olivero_account_menu/disable"]');
|
||||
});
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Verify no console errors': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/')
|
||||
.waitForElementVisible('body')
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function () {
|
||||
return Drupal.errorLog.length === 0;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.ok(result.value, 'Verify no console errors exist.');
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,129 @@
|
||||
const mobileNavButtonSelector = 'button.mobile-nav-button';
|
||||
const headerNavSelector = '#header-nav';
|
||||
const searchButtonSelector = 'button.block-search-wide__button';
|
||||
const searchFormSelector = '.search-form.search-block-form';
|
||||
const searchWideSelector = '.block-search-wide__wrapper';
|
||||
const searchWideInputSelector = '#edit-keys--2';
|
||||
const searchNarrowSelector = '.block-search-narrow';
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
})
|
||||
// Create user that can search.
|
||||
.drupalCreateUser({
|
||||
name: 'user',
|
||||
password: '123',
|
||||
permissions: ['search content', 'use advanced search'],
|
||||
})
|
||||
.drupalLogin({ name: 'user', password: '123' });
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'search wide form is accessible and altered': (browser) => {
|
||||
browser
|
||||
.setWindowSize(1400, 800)
|
||||
.drupalRelativeURL('/')
|
||||
.waitForElementVisible(searchButtonSelector)
|
||||
.assert.attributeEquals(searchButtonSelector, 'aria-expanded', 'false')
|
||||
.click(searchButtonSelector)
|
||||
.waitForElementVisible(searchWideInputSelector)
|
||||
.assert.attributeEquals(searchButtonSelector, 'aria-expanded', 'true')
|
||||
.assert.attributeContains(
|
||||
searchWideInputSelector,
|
||||
'placeholder',
|
||||
'Search by keyword or phrase.',
|
||||
)
|
||||
.assert.attributeContains(
|
||||
searchWideInputSelector,
|
||||
'title',
|
||||
'Enter the terms you wish to search for.',
|
||||
)
|
||||
.assert.elementPresent('button.search-form__submit')
|
||||
// Assert wide search form closes when element moves to body.
|
||||
.click('body')
|
||||
.waitForElementNotVisible(searchWideSelector)
|
||||
.assert.attributeEquals(searchButtonSelector, 'aria-expanded', 'false');
|
||||
},
|
||||
'Test focus management': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/')
|
||||
.waitForElementVisible(searchButtonSelector)
|
||||
.click(searchButtonSelector)
|
||||
.waitForElementVisible(searchWideInputSelector)
|
||||
.pause(400) // Wait for transitionend event to fire.
|
||||
// Assert that focus is moved to wide search text input.
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (searchWideInputSelector) {
|
||||
return document.activeElement.matches(searchWideInputSelector);
|
||||
},
|
||||
[searchWideInputSelector],
|
||||
(result) => {
|
||||
browser.assert.ok(
|
||||
result.value,
|
||||
'Assert that focus moves to wide search form on open.',
|
||||
);
|
||||
},
|
||||
)
|
||||
// Assert that search form is still visible when focus is on disclosure button.
|
||||
.perform(function () {
|
||||
return this.actions()
|
||||
.keyDown(browser.Keys.SHIFT)
|
||||
.sendKeys(browser.Keys.TAB);
|
||||
})
|
||||
.pause(50)
|
||||
.isVisible(searchWideSelector)
|
||||
// Assert that search form is NOT visible when focus moves back to menu item.
|
||||
.perform(function () {
|
||||
return this.actions().sendKeys(browser.Keys.TAB);
|
||||
})
|
||||
.pause(50)
|
||||
.waitForElementNotVisible(searchWideSelector)
|
||||
// Release SHIFT key.
|
||||
.perform(function () {
|
||||
return this.actions().keyUp(browser.Keys.SHIFT);
|
||||
});
|
||||
},
|
||||
'search narrow form is accessible': (browser) => {
|
||||
browser
|
||||
.setWindowSize(1000, 800)
|
||||
.drupalRelativeURL('/')
|
||||
.click(mobileNavButtonSelector)
|
||||
.waitForElementVisible(headerNavSelector)
|
||||
.waitForElementVisible(`${searchNarrowSelector} ${searchFormSelector}`);
|
||||
},
|
||||
'submit button styled as primary on forms with <= 2 actions': (browser) => {
|
||||
browser
|
||||
.setWindowSize(1400, 800)
|
||||
.drupalRelativeURL('/form-test/object-controller-builder')
|
||||
.assert.elementPresent(
|
||||
'#edit-actions input[type=submit].button--primary',
|
||||
);
|
||||
},
|
||||
'search page is altered': (browser) => {
|
||||
browser
|
||||
.setWindowSize(1400, 800)
|
||||
.drupalRelativeURL('/search')
|
||||
.assert.attributeContains(
|
||||
'.search-form input[name=keys]',
|
||||
'placeholder',
|
||||
'Search by keyword or phrase.',
|
||||
)
|
||||
.assert.attributeContains(
|
||||
'.search-form input[name=keys]',
|
||||
'title',
|
||||
'Enter the terms you wish to search for.',
|
||||
)
|
||||
.assert.elementPresent('#edit-basic input[type=submit].button--primary')
|
||||
.assert.elementPresent(
|
||||
'#edit-advanced input[type=submit].button--primary',
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,35 @@
|
||||
const buttonSelector = 'button.sticky-header-toggle';
|
||||
const mainMenuSelector = '#block-olivero-main-menu';
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'olivero'],
|
||||
before(browser) {
|
||||
browser.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteOliveroInstallTestScript.php',
|
||||
installProfile: 'minimal',
|
||||
});
|
||||
browser.window.setSize(1400, 800);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'On scroll, menu collapses to burger 🍔 menu': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/node')
|
||||
.assert.not.visible(buttonSelector)
|
||||
.assert.attributeEquals(buttonSelector, 'aria-checked', 'false')
|
||||
.click('.block-system-powered-by-block .drupal-logo')
|
||||
.assert.visible(buttonSelector)
|
||||
.assert.not.visible('#site-header__inner')
|
||||
.assert.not.visible(mainMenuSelector)
|
||||
.click(buttonSelector)
|
||||
.assert.visible(mainMenuSelector)
|
||||
.assert.attributeEquals(buttonSelector, 'aria-checked', 'true')
|
||||
|
||||
// Sticky header should remain open after page reload in open state.
|
||||
.drupalRelativeURL('/node')
|
||||
.assert.visible(mainMenuSelector)
|
||||
.assert.attributeEquals(buttonSelector, 'aria-checked', 'true');
|
||||
},
|
||||
};
|
||||
84
web/core/tests/Drupal/Nightwatch/Tests/a11yTestAdmin.js
Normal file
84
web/core/tests/Drupal/Nightwatch/Tests/a11yTestAdmin.js
Normal file
@ -0,0 +1,84 @@
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
|
||||
const adminTest = {
|
||||
'@tags': ['core', 'a11y', 'a11y:admin'],
|
||||
|
||||
before(browser) {
|
||||
browser.drupalInstall({ installProfile: 'nightwatch_a11y_testing' });
|
||||
// If an admin theme other than Claro is being used for testing, install it.
|
||||
if (argv.adminTheme && argv.adminTheme !== browser.globals.adminTheme) {
|
||||
browser.drupalEnableTheme(argv.adminTheme, true);
|
||||
}
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
};
|
||||
const testCases = [
|
||||
{ name: 'User Edit', path: '/user/1/edit' },
|
||||
{
|
||||
name: 'Create Article',
|
||||
path: '/node/add/article?destination=/admin/content',
|
||||
},
|
||||
{ name: 'Create Page', path: '/node/add/page?destination=/admin/content' },
|
||||
{ name: 'Content Page', path: '/admin/content' },
|
||||
{ name: 'Structure Page', path: '/admin/structure' },
|
||||
{ name: 'Add content type', path: '/admin/structure/types/add' },
|
||||
{ name: 'Add vocabulary', path: '/admin/structure/taxonomy/add' },
|
||||
{
|
||||
// Tests long breadcrumb for https://drupal.org/i/3223147.
|
||||
name: 'Manage text format, mobile',
|
||||
path: '/admin/config/content/formats/manage/restricted_html',
|
||||
windowSize: {
|
||||
// Dimensions used by Lighthouse for mobile.
|
||||
width: 415,
|
||||
height: 823,
|
||||
},
|
||||
options: {
|
||||
runOnly: {
|
||||
type: 'tag',
|
||||
values: [
|
||||
'wcag2a',
|
||||
'wcag2aa',
|
||||
'wcag21a',
|
||||
'wcag21aa',
|
||||
'best-practice',
|
||||
'wcag22a',
|
||||
'wcag22aa',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
// @todo remove the skipped rules below in https://drupal.org/i/3318394.
|
||||
{
|
||||
name: 'Structure | Block',
|
||||
path: '/admin/structure/block',
|
||||
options: {
|
||||
rules: {
|
||||
'color-contrast': { enabled: false },
|
||||
'duplicate-id-active': { enabled: false },
|
||||
region: { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
adminTest[`Accessibility - Admin Theme: ${testCase.name}`] = (browser) => {
|
||||
if (testCase.windowSize) {
|
||||
browser.setWindowSize(
|
||||
testCase.windowSize.width,
|
||||
testCase.windowSize.height,
|
||||
);
|
||||
}
|
||||
|
||||
browser.drupalLoginAsAdmin(() => {
|
||||
browser
|
||||
.drupalRelativeURL(testCase.path)
|
||||
.axeInject()
|
||||
.axeRun('body', testCase.options || {});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = adminTest;
|
||||
67
web/core/tests/Drupal/Nightwatch/Tests/a11yTestDefault.js
Normal file
67
web/core/tests/Drupal/Nightwatch/Tests/a11yTestDefault.js
Normal file
@ -0,0 +1,67 @@
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
|
||||
const a11yThemeTest = {
|
||||
'@tags': ['core', 'a11y', 'a11y:default'],
|
||||
|
||||
before(browser) {
|
||||
browser.drupalInstall({ installProfile: 'nightwatch_a11y_testing' });
|
||||
// If the default theme is set to something other than Olivero, install it.
|
||||
if (
|
||||
argv.defaultTheme &&
|
||||
argv.defaultTheme !== browser.globals.defaultTheme
|
||||
) {
|
||||
browser.drupalEnableTheme(argv.defaultTheme);
|
||||
}
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
};
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
name: 'Homepage',
|
||||
path: '/',
|
||||
// @todo remove the disabled 'region' rule in https://drupal.org/i/3318396.
|
||||
options: {
|
||||
rules: {
|
||||
region: { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Login',
|
||||
path: '/user/login',
|
||||
// @todo remove the disabled 'region' rule in https://drupal.org/i/3318396.
|
||||
options: {
|
||||
rules: {
|
||||
region: { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
// @todo remove the heading and duplicate id rules below in
|
||||
// https://drupal.org/i/3318398.
|
||||
{
|
||||
name: 'Search',
|
||||
path: '/search/node',
|
||||
options: {
|
||||
rules: {
|
||||
'heading-order': { enabled: false },
|
||||
'duplicate-id-aria': { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
a11yThemeTest[`Accessibility - Default Theme: ${testCase.name}`] = (
|
||||
browser,
|
||||
) => {
|
||||
browser
|
||||
.drupalRelativeURL(testCase.path)
|
||||
.axeInject()
|
||||
.axeRun('body', testCase.options || {});
|
||||
};
|
||||
});
|
||||
|
||||
module.exports = a11yThemeTest;
|
||||
@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
'@tags': ['core', 'ajax'],
|
||||
before(browser) {
|
||||
browser.drupalInstall().drupalInstallModule('ajax_test', true);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Test Execution Order': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/ajax-test/promise-form')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.click('[data-drupal-selector="edit-test-execution-order-button"]')
|
||||
.waitForElementVisible('#ajax_test_form_promise_wrapper', 1000)
|
||||
.assert.textContains(
|
||||
'#ajax_test_form_promise_wrapper',
|
||||
'12345',
|
||||
'Ajax commands execution order confirmed',
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,67 @@
|
||||
// cspell:ignore is-autocompleting
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
|
||||
before(browser) {
|
||||
browser
|
||||
.drupalInstall()
|
||||
.drupalInstallModule('form_test', true)
|
||||
.drupalLoginAsAdmin(() => {
|
||||
browser
|
||||
.drupalRelativeURL('/admin/appearance')
|
||||
.click('[title="Install Claro as default theme"]')
|
||||
.waitForElementVisible('.system-themes-list', 10000); // Confirm installation.
|
||||
});
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
|
||||
'Test Claro autocomplete': (browser) => {
|
||||
browser
|
||||
.drupalCreateUser({
|
||||
name: 'user',
|
||||
password: '123',
|
||||
permissions: ['access autocomplete test'],
|
||||
})
|
||||
.drupalLogin({ name: 'user', password: '123' })
|
||||
.drupalRelativeURL('/form-test/autocomplete')
|
||||
.waitForElementVisible('body', 1000);
|
||||
|
||||
// Tests that entering a character from the
|
||||
// data-autocomplete-first-character-denylist doesn't start the
|
||||
// autocomplete process.
|
||||
browser
|
||||
.setValue('[name="autocomplete_4"]', '/')
|
||||
.pause(1000)
|
||||
.waitForElementNotPresent('.is-autocompleting');
|
||||
|
||||
// Tests both the autocomplete-message nor the autocomplete dropdown are
|
||||
// present when nothing has been entered in autocomplete-3.
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
browser.expect.element(
|
||||
'.js-form-item-autocomplete-3 [data-drupal-selector="autocomplete-message"]',
|
||||
).to.not.visible;
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
browser.expect.element('#ui-id-3.ui-autocomplete').to.not.visible;
|
||||
|
||||
// Tests that upon entering some text in autocomplete-3, first the
|
||||
// autocomplete-message appears and then the autocomplete dropdown with a
|
||||
// result. At that point the autocomplete-message should be invisible again.
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
browser
|
||||
.setValue('[name="autocomplete_3"]', '123')
|
||||
.waitForElementVisible(
|
||||
'.js-form-item-autocomplete-3 [data-drupal-selector="autocomplete-message"]',
|
||||
)
|
||||
.waitForElementVisible('#ui-id-3.ui-autocomplete')
|
||||
.expect.element(
|
||||
'.js-form-item-autocomplete-3 [data-drupal-selector="autocomplete-message"]',
|
||||
).to.not.visible;
|
||||
|
||||
browser.drupalLogAndEnd({ onlyOnError: false });
|
||||
},
|
||||
};
|
||||
123
web/core/tests/Drupal/Nightwatch/Tests/htmx/htmxTest.js
Normal file
123
web/core/tests/Drupal/Nightwatch/Tests/htmx/htmxTest.js
Normal file
@ -0,0 +1,123 @@
|
||||
// The javascript that creates dropbuttons is not present on the /page at
|
||||
// initial load. If the once data property is added then the JS was loaded
|
||||
// and triggered on the inserted content.
|
||||
// @see \Drupal\test_htmx\Controller\HtmxTestAttachmentsController
|
||||
// @see core/modules/system/tests/modules/test_htmx/js/reveal-merged-settings.js
|
||||
|
||||
const scriptSelector = 'script[src*="test_htmx/js/behavior.js"]';
|
||||
const cssSelector = 'link[rel="stylesheet"][href*="test_htmx/css/style.css"]';
|
||||
const elementSelector = '.ajax-content';
|
||||
const elementInitSelector = `${elementSelector}[data-once="htmx-init"]`;
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core', 'htmx'],
|
||||
before(browser) {
|
||||
browser.drupalInstall({
|
||||
setupFile: 'core/tests/Drupal/TestSite/HtmxAssetLoadTestSetup.php',
|
||||
installProfile: 'minimal',
|
||||
});
|
||||
},
|
||||
afterEach(browser) {
|
||||
browser.drupalLogAndEnd({ onlyOnError: true });
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
|
||||
'Asset Load': (browser) => {
|
||||
// Load the route htmx will use for the request on click and confirm the
|
||||
// markup we will be looking for is present in the source markup.
|
||||
browser
|
||||
.drupalRelativeURL('/htmx-test-attachments/replace')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.elementPresent(elementInitSelector);
|
||||
// Now load the page with the htmx enhanced button and verify the absence
|
||||
// of the markup to be inserted. Click the button
|
||||
// and check for inserted javascript and markup.
|
||||
browser
|
||||
.drupalRelativeURL('/htmx-test-attachments/page')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.not.elementPresent(scriptSelector)
|
||||
.assert.not.elementPresent(cssSelector)
|
||||
.waitForElementVisible('[name="replace"]', 1000)
|
||||
.click('[name="replace"]')
|
||||
.waitForElementVisible(elementSelector, 1100)
|
||||
.waitForElementVisible(elementInitSelector, 1100)
|
||||
.assert.elementPresent(scriptSelector)
|
||||
.assert.elementPresent(cssSelector);
|
||||
},
|
||||
|
||||
'Swap Before': (browser) => {
|
||||
// Load the route htmx will use for the request on click and confirm the
|
||||
// markup we will be looking for is present in the source markup.
|
||||
browser
|
||||
.drupalRelativeURL('/htmx-test-attachments/replace')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.elementPresent(elementInitSelector);
|
||||
// Now load the page with the htmx enhanced button and verify the absence
|
||||
// of the markup to be inserted. Click the button
|
||||
// and check for inserted javascript and markup.
|
||||
browser
|
||||
.drupalRelativeURL('/htmx-test-attachments/before')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.not.elementPresent(scriptSelector)
|
||||
.assert.not.elementPresent(cssSelector)
|
||||
.waitForElementVisible('[name="replace"]', 1000)
|
||||
.click('[name="replace"]')
|
||||
.waitForElementVisible(elementSelector, 1100)
|
||||
.waitForElementVisible(elementInitSelector, 1100)
|
||||
.assert.elementPresent(scriptSelector)
|
||||
.assert.elementPresent(cssSelector);
|
||||
},
|
||||
|
||||
'Swap After': (browser) => {
|
||||
// Load the route htmx will use for the request on click and confirm the
|
||||
// markup we will be looking for is present in the source markup.
|
||||
browser
|
||||
.drupalRelativeURL('/htmx-test-attachments/replace')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.elementPresent(elementInitSelector);
|
||||
// Now load the page with the htmx enhanced button and verify the absence
|
||||
// of the markup to be inserted. Click the button
|
||||
// and check for inserted javascript and markup.
|
||||
browser
|
||||
.drupalRelativeURL('/htmx-test-attachments/after')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.not.elementPresent(scriptSelector)
|
||||
.assert.not.elementPresent(cssSelector)
|
||||
.waitForElementVisible('[name="replace"]', 1000)
|
||||
.click('[name="replace"]')
|
||||
.waitForElementVisible(elementSelector, 1100)
|
||||
.waitForElementVisible(elementInitSelector, 1100)
|
||||
.assert.elementPresent(scriptSelector)
|
||||
.assert.elementPresent(cssSelector);
|
||||
},
|
||||
|
||||
'Ajax Load HTMX Element': (browser) => {
|
||||
// Load the route htmx will use for the request on click and confirm the
|
||||
// markup we will be looking for is present in the source markup.
|
||||
browser
|
||||
.drupalRelativeURL('/htmx-test-attachments/replace')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.elementPresent(scriptSelector);
|
||||
// Now load the page with the ajax powered button. Click the button
|
||||
// to insert an htmx enhanced button and verify the absence
|
||||
// of the markup to be inserted. Click the button
|
||||
// and check for inserted javascript and markup.
|
||||
browser
|
||||
.drupalRelativeURL('/htmx-test-attachments/ajax')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.not.elementPresent(scriptSelector)
|
||||
.assert.not.elementPresent(cssSelector)
|
||||
.waitForElementVisible('[data-drupal-selector="edit-ajax-button"]', 1000)
|
||||
.pause(1000)
|
||||
.click('[data-drupal-selector="edit-ajax-button"]')
|
||||
.waitForElementVisible('[name="replace"]', 1000)
|
||||
.pause(1000)
|
||||
.click('[name="replace"]')
|
||||
.waitForElementVisible(elementSelector, 1100)
|
||||
.waitForElementVisible(elementInitSelector, 1100)
|
||||
.assert.elementPresent(scriptSelector)
|
||||
.assert.elementPresent(cssSelector);
|
||||
},
|
||||
};
|
||||
19
web/core/tests/Drupal/Nightwatch/Tests/installProfileTest.js
Normal file
19
web/core/tests/Drupal/Nightwatch/Tests/installProfileTest.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
before(browser) {
|
||||
browser.drupalInstall({
|
||||
setupFile: 'core/tests/Drupal/TestSite/TestSiteInstallTestScript.php',
|
||||
installProfile: 'demo_umami',
|
||||
});
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Test umami profile': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/test-page')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.elementPresent('#block-umami-branding')
|
||||
.drupalLogAndEnd({ onlyOnError: false });
|
||||
},
|
||||
};
|
||||
2388
web/core/tests/Drupal/Nightwatch/Tests/jQueryUIPositionShimTest.js
Normal file
2388
web/core/tests/Drupal/Nightwatch/Tests/jQueryUIPositionShimTest.js
Normal file
File diff suppressed because it is too large
Load Diff
22
web/core/tests/Drupal/Nightwatch/Tests/jsDeprecationTest.js
Normal file
22
web/core/tests/Drupal/Nightwatch/Tests/jsDeprecationTest.js
Normal file
@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
before(browser) {
|
||||
browser.drupalInstall().drupalInstallModule('js_deprecation_test');
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Test JavaScript deprecations': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/js_deprecation_test')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.textContains('h1', 'JsDeprecationTest')
|
||||
.assert.deprecationErrorExists(
|
||||
'This function is deprecated for testing purposes.',
|
||||
)
|
||||
.assert.deprecationErrorExists(
|
||||
'This property is deprecated for testing purposes.',
|
||||
)
|
||||
.drupalLogAndEnd({ onlyOnError: false });
|
||||
},
|
||||
};
|
||||
126
web/core/tests/Drupal/Nightwatch/Tests/jsDisplace.js
Normal file
126
web/core/tests/Drupal/Nightwatch/Tests/jsDisplace.js
Normal file
@ -0,0 +1,126 @@
|
||||
const testValues = {
|
||||
top: 200,
|
||||
right: 110,
|
||||
bottom: 145,
|
||||
left: 310,
|
||||
};
|
||||
|
||||
const testElements = `
|
||||
<div
|
||||
data-offset-top
|
||||
style="
|
||||
background-color: red;
|
||||
height: 110px;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 90px;
|
||||
width: 100%;"
|
||||
></div>
|
||||
|
||||
<div
|
||||
data-offset-right
|
||||
style="
|
||||
background-color: blue;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
width: 100px;"
|
||||
></div>
|
||||
|
||||
<div
|
||||
data-offset-bottom
|
||||
style="
|
||||
background-color: yellow;
|
||||
bottom: 45px;
|
||||
height: 100px;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
width: 100%;"
|
||||
></div>
|
||||
|
||||
<div
|
||||
data-offset-left
|
||||
style="
|
||||
background-color: orange;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
left: 10px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 300px;"
|
||||
></div>
|
||||
`;
|
||||
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
before(browser) {
|
||||
browser.drupalInstall().drupalInstallModule('js_displace');
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Test Drupal.displace() JavaScript API': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/')
|
||||
.waitForElementVisible('body')
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (testValues, testElements) {
|
||||
const testElementsContainer = document.createElement('div');
|
||||
|
||||
testElementsContainer.innerHTML = testElements;
|
||||
document.body.append(testElementsContainer);
|
||||
|
||||
const displaceOutput = Drupal.displace();
|
||||
return (
|
||||
displaceOutput.top === testValues.top &&
|
||||
displaceOutput.right === testValues.right &&
|
||||
displaceOutput.bottom === testValues.bottom &&
|
||||
displaceOutput.left === testValues.left
|
||||
);
|
||||
},
|
||||
[testValues, testElements],
|
||||
(result) => {
|
||||
browser.assert.ok(
|
||||
result.value,
|
||||
'Drupal.displace() JS returns proper offsets for all edges.',
|
||||
);
|
||||
},
|
||||
)
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (testValues) {
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
const topOffsetStyle = rootStyles.getPropertyValue(
|
||||
'--drupal-displace-offset-top',
|
||||
);
|
||||
const rightOffsetStyle = rootStyles.getPropertyValue(
|
||||
'--drupal-displace-offset-right',
|
||||
);
|
||||
const bottomOffsetStyle = rootStyles.getPropertyValue(
|
||||
'--drupal-displace-offset-bottom',
|
||||
);
|
||||
const leftOffsetStyle = rootStyles.getPropertyValue(
|
||||
'--drupal-displace-offset-left',
|
||||
);
|
||||
return (
|
||||
topOffsetStyle === `${testValues.top}px` &&
|
||||
rightOffsetStyle === `${testValues.right}px` &&
|
||||
bottomOffsetStyle === `${testValues.bottom}px` &&
|
||||
leftOffsetStyle === `${testValues.left}px`
|
||||
);
|
||||
},
|
||||
[testValues],
|
||||
(result) => {
|
||||
browser.assert.ok(
|
||||
result.value,
|
||||
'Drupal.displace() properly sets CSS variables.',
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
125
web/core/tests/Drupal/Nightwatch/Tests/jsOnceTest.js
Normal file
125
web/core/tests/Drupal/Nightwatch/Tests/jsOnceTest.js
Normal file
@ -0,0 +1,125 @@
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
before(browser) {
|
||||
browser.drupalInstall().drupalInstallModule('js_once_test');
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Test simple once call': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/js_once_test')
|
||||
.waitForElementVisible('[data-drupal-item]', 1000)
|
||||
// prettier-ignore
|
||||
.execute(
|
||||
function () {
|
||||
return once('js_once_test', '[data-drupal-item]');
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.strictEqual(
|
||||
result.value.length,
|
||||
5,
|
||||
'5 items returned and "once-d"',
|
||||
);
|
||||
},
|
||||
)
|
||||
// Check that follow-up calls to once return an empty array.
|
||||
.execute(
|
||||
function () {
|
||||
return once('js_once_test', '[data-drupal-item]');
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.strictEqual(
|
||||
result.value.length,
|
||||
0,
|
||||
'0 items returned',
|
||||
);
|
||||
},
|
||||
)
|
||||
.execute(
|
||||
function () {
|
||||
return once(
|
||||
'js_once_test_extra',
|
||||
'[data-drupal-item="1"],[data-drupal-item="2"]',
|
||||
);
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.strictEqual(
|
||||
result.value.length,
|
||||
2,
|
||||
'2 items returned and "once-d"',
|
||||
);
|
||||
},
|
||||
)
|
||||
.execute(
|
||||
function () {
|
||||
return once(
|
||||
'js_once_test_extra',
|
||||
'[data-drupal-item="1"],[data-drupal-item="2"]',
|
||||
);
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.strictEqual(
|
||||
result.value.length,
|
||||
0,
|
||||
'0 items returned',
|
||||
);
|
||||
},
|
||||
)
|
||||
.execute(
|
||||
function () {
|
||||
return once.remove('js_once_test', '[data-drupal-item]');
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.strictEqual(
|
||||
result.value.length,
|
||||
5,
|
||||
'5 items returned and "de-once-d"',
|
||||
);
|
||||
},
|
||||
)
|
||||
.execute(
|
||||
function () {
|
||||
return once.remove('js_once_test', '[data-drupal-item]');
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.strictEqual(
|
||||
result.value.length,
|
||||
0,
|
||||
'0 items returned',
|
||||
);
|
||||
},
|
||||
)
|
||||
.execute(
|
||||
function () {
|
||||
return once.remove(
|
||||
'js_once_test_extra',
|
||||
'[data-drupal-item="1"],[data-drupal-item="2"]',
|
||||
);
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.strictEqual(
|
||||
result.value.length,
|
||||
2,
|
||||
'2 items returned and "de-once-d"',
|
||||
);
|
||||
},
|
||||
)
|
||||
.execute(
|
||||
function () {
|
||||
return once.remove(
|
||||
'js_once_test_extra',
|
||||
'[data-drupal-item="1"],[data-drupal-item="2"]',
|
||||
);
|
||||
},
|
||||
(result) => {
|
||||
browser.assert.strictEqual(
|
||||
result.value.length,
|
||||
0,
|
||||
'0 items returned',
|
||||
);
|
||||
},
|
||||
)
|
||||
.drupalLogAndEnd({ onlyOnError: false });
|
||||
},
|
||||
};
|
||||
19
web/core/tests/Drupal/Nightwatch/Tests/langcodeTest.js
Normal file
19
web/core/tests/Drupal/Nightwatch/Tests/langcodeTest.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
before(browser) {
|
||||
browser.drupalInstall({
|
||||
setupFile:
|
||||
'core/tests/Drupal/TestSite/TestSiteMultilingualInstallTestScript.php',
|
||||
langcode: 'fr',
|
||||
});
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Test page with langcode': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/test-page')
|
||||
.assert.attributeEquals('html', 'lang', 'fr')
|
||||
.drupalLogAndEnd({ onlyOnError: false });
|
||||
},
|
||||
};
|
||||
24
web/core/tests/Drupal/Nightwatch/Tests/loginTest.js
Normal file
24
web/core/tests/Drupal/Nightwatch/Tests/loginTest.js
Normal file
@ -0,0 +1,24 @@
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
|
||||
before(browser) {
|
||||
browser.drupalInstall();
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
|
||||
'Test login': (browser) => {
|
||||
browser
|
||||
.drupalCreateUser({
|
||||
name: 'user',
|
||||
password: '123',
|
||||
permissions: ['access site reports', 'administer site configuration'],
|
||||
})
|
||||
.drupalLogin({ name: 'user', password: '123' })
|
||||
.drupalRelativeURL('/admin/reports')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.assert.textContains('h1', 'Reports')
|
||||
.assert.noDeprecationErrors();
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,262 @@
|
||||
// cSpell:disable
|
||||
const MachineNameTestArray = [
|
||||
{
|
||||
machineName: 'Bob',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'bob',
|
||||
},
|
||||
{
|
||||
machineName: 'Äwesome',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'awesome',
|
||||
},
|
||||
{
|
||||
machineName: 'B?!"@\\/-ob@e',
|
||||
replacePattern: '[^a-zA-Z0-9_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'b_ob_e',
|
||||
},
|
||||
{
|
||||
machineName: 'Bob@e\\0',
|
||||
replacePattern: '[^a-zA-Z0-9_.~@]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'bob@e_0',
|
||||
},
|
||||
{
|
||||
machineName: 'Bobby',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'bobby',
|
||||
},
|
||||
{
|
||||
machineName: 'ǍǎǏ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'aai',
|
||||
},
|
||||
// The expected machine name are modified because we don't have
|
||||
// the removeDiacritics() function present in PhpTranliteration.php.
|
||||
{
|
||||
machineName: 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'aaaaaaaeceeeeiiii',
|
||||
},
|
||||
{
|
||||
machineName: 'ÐÑÒÓÔÕÖרÙÚÛÜÝÞß',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'dnoooooxouuuuuthss',
|
||||
},
|
||||
{
|
||||
machineName: 'àáâãäåæçèéêëìíîï',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'aaaaaaaeceeeeiiii',
|
||||
},
|
||||
{
|
||||
machineName: 'ðñòóôõö÷øùúûüýþÿ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'dnooooo_ouuuuythy',
|
||||
},
|
||||
{
|
||||
machineName: 'ĀāĂ㥹ĆćĈĉĊċČčĎď',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'aaaaaaccccccccdd',
|
||||
},
|
||||
{
|
||||
machineName: 'ĐđĒēĔĕĖėĘęĚěĜĝĞğ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'ddeeeeeeeeeegggg',
|
||||
},
|
||||
{
|
||||
machineName: 'ĠġĢģĤĥĦħĨĩĪīĬĭĮį',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'gggghhhhiiiiiiii',
|
||||
},
|
||||
{
|
||||
machineName: 'İıIJijĴĵĶķĸĹĺĻļĽľĿ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'iiijijjjkkklllllll',
|
||||
},
|
||||
{
|
||||
machineName: 'ŀŁłŃńŅņŇňʼnŊŋŌōŎŏ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'lllnnnnnn_nngngoooo',
|
||||
},
|
||||
{
|
||||
machineName: 'ŐőŒœŔŕŖŗŘřŚśŜŝŞş',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'oooeoerrrrrrssssss',
|
||||
},
|
||||
{
|
||||
machineName: 'ŠšŢţŤťŦŧŨũŪūŬŭŮů',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'ssttttttuuuuuuuu',
|
||||
},
|
||||
{
|
||||
machineName: 'ŰűŲųŴŵŶŷŸŹźŻżŽž',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'uuuuwwyyyzzzzzz',
|
||||
},
|
||||
{
|
||||
machineName: 'ǐǑǒǓǔǕǖǗǘǙǚǛǜǝǞǟ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'ioouuuuuuuuuu_aa',
|
||||
},
|
||||
{
|
||||
machineName: 'ǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'aaaeaeggggkkoooozhzh',
|
||||
},
|
||||
{
|
||||
machineName: 'ǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'jdzddzgghvwnnaaaeaeoo',
|
||||
},
|
||||
{
|
||||
machineName: 'ȀȁȂȃȄȅȆȇȈȉȊȋȌȍȎȏ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'aaaaeeeeiiiioooo',
|
||||
},
|
||||
{
|
||||
machineName: 'ȐȑȒȓȔȕȖȗȘșȚțȜȝȞȟ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'rrrruuuussttyyhh',
|
||||
},
|
||||
{
|
||||
machineName: 'ȠȡȢȣȤȥȦȧȨȩȪȫȬȭȮȯ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'ndououzzaaeeoooooo',
|
||||
},
|
||||
{
|
||||
machineName: 'ȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'ooyylntjdbqpacclts',
|
||||
},
|
||||
{
|
||||
machineName: 'ɀɁɂɃɄɅɆɇɈɉɊɋɌɍɎɏ',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'z_buveejjqqrryy',
|
||||
},
|
||||
// Test for maximum length of machine-name
|
||||
{
|
||||
machineName: 'This is the test for max length',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 20,
|
||||
expectedMachineName: 'this_is_the_test_for',
|
||||
},
|
||||
{
|
||||
machineName: 'Ma@Chi!~',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '_',
|
||||
maxlength: 5,
|
||||
expectedMachineName: 'ma_ch',
|
||||
},
|
||||
{
|
||||
machineName: 'Test for custom replace character',
|
||||
replacePattern: '[^a-zA-Z0-9-_.~]+',
|
||||
replaceChar: '-',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'test-for-custom-replace-character',
|
||||
},
|
||||
{
|
||||
machineName: 'Test for unusual replace pattern',
|
||||
replacePattern: '([^a-z0-9_]+)|(^custom$)',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: 'test_for_unusual_replace_pattern',
|
||||
},
|
||||
{
|
||||
machineName: 'custom',
|
||||
replacePattern: '([^a-z0-9_]+)|(^custom$)',
|
||||
replaceChar: '_',
|
||||
maxlength: 64,
|
||||
expectedMachineName: '_',
|
||||
},
|
||||
// cSpell:enable
|
||||
];
|
||||
module.exports = {
|
||||
before(browser) {
|
||||
browser.drupalInstall().drupalLoginAsAdmin(() => {
|
||||
browser
|
||||
.drupalRelativeURL('/admin/modules')
|
||||
.setValue('input[type="search"]', 'FormAPI')
|
||||
.waitForElementVisible('input[name="modules[form_test][enable]"]', 1000)
|
||||
.click('input[name="modules[form_test][enable]"]')
|
||||
.click('input[type="submit"]') // Submit module form.
|
||||
.click('input[type="submit"]'); // Confirm installation of dependencies.
|
||||
});
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Machine name generation test': (browser) => {
|
||||
browser.drupalRelativeURL('/form-test/machine-name');
|
||||
MachineNameTestArray.forEach((iteration) => {
|
||||
browser.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
|
||||
function (object) {
|
||||
return Drupal.behaviors.machineName.transliterate(
|
||||
object.machineName,
|
||||
{
|
||||
replace_pattern: object.replacePattern,
|
||||
replace: object.replaceChar,
|
||||
maxlength: object.maxlength,
|
||||
},
|
||||
);
|
||||
},
|
||||
[iteration],
|
||||
(result) => {
|
||||
browser.assert.equal(result.value, iteration.expectedMachineName);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
37
web/core/tests/Drupal/Nightwatch/Tests/statesTest.js
Normal file
37
web/core/tests/Drupal/Nightwatch/Tests/statesTest.js
Normal file
@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
before(browser) {
|
||||
browser.drupalInstall().drupalInstallModule('form_test', true);
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'Test form with state API': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/form-test/javascript-states-form')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.waitForElementNotVisible('input[name="textfield"]', 1000)
|
||||
.assert.noDeprecationErrors();
|
||||
},
|
||||
'Test number trigger with spinner widget': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/form-test/javascript-states-form')
|
||||
.waitForElementVisible('body', 1000)
|
||||
.waitForElementNotVisible(
|
||||
'#edit-item-visible-when-number-trigger-filled-by-spinner',
|
||||
1000,
|
||||
)
|
||||
.execute(() => {
|
||||
// Emulate usage of the spinner browser widget on number inputs
|
||||
// on modern browsers.
|
||||
const numberTrigger = document.getElementById('edit-number-trigger');
|
||||
numberTrigger.value = 1;
|
||||
numberTrigger.dispatchEvent(new Event('change'));
|
||||
});
|
||||
|
||||
browser.waitForElementVisible(
|
||||
'#edit-item-visible-when-number-trigger-filled-by-spinner',
|
||||
1000,
|
||||
);
|
||||
},
|
||||
};
|
||||
182
web/core/tests/Drupal/Nightwatch/Tests/tabbingManagerTest.js
Normal file
182
web/core/tests/Drupal/Nightwatch/Tests/tabbingManagerTest.js
Normal file
@ -0,0 +1,182 @@
|
||||
module.exports = {
|
||||
'@tags': ['core'],
|
||||
before(browser) {
|
||||
browser.drupalInstall().drupalInstallModule('tabbingmanager_test');
|
||||
},
|
||||
after(browser) {
|
||||
browser.drupalUninstall();
|
||||
},
|
||||
'test tabbingmanager': (browser) => {
|
||||
browser
|
||||
.drupalRelativeURL('/tabbingmanager-test')
|
||||
.waitForElementPresent('#tabbingmanager-test-container', 1000);
|
||||
|
||||
// Tab through the form without tabbing constrained. Tabbing out of the
|
||||
// third input should focus the fourth.
|
||||
browser
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
document.querySelector('#first').focus();
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'first',
|
||||
'[not constrained] First element focused after calling focus().',
|
||||
);
|
||||
},
|
||||
)
|
||||
.setValue('#first', [browser.Keys.TAB])
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'second',
|
||||
'[not constrained] Tabbing first element focuses second element.',
|
||||
);
|
||||
},
|
||||
)
|
||||
.setValue('#second', [browser.Keys.TAB])
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'third',
|
||||
'[not constrained] Tabbing second element focuses third element.',
|
||||
);
|
||||
},
|
||||
)
|
||||
.setValue('#third', [browser.Keys.TAB])
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'fourth',
|
||||
'[not constrained] Tabbing third element focuses fourth element.',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Tab through the form with tabbing constrained to the container that has
|
||||
// the first, second, and third inputs. Tabbing out of the third (final)
|
||||
// input should move focus back to the first one.
|
||||
browser
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
Drupal.tabbingManager.constrain(
|
||||
document.querySelector('#tabbingmanager-test-container'),
|
||||
{ trapFocus: true },
|
||||
);
|
||||
document.querySelector('#first').focus();
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'first',
|
||||
'[constrained] First element focused after calling focus().',
|
||||
);
|
||||
},
|
||||
)
|
||||
.setValue('#first', [browser.Keys.TAB])
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'second',
|
||||
'[constrained] Tabbing first element focuses second element',
|
||||
);
|
||||
},
|
||||
)
|
||||
.setValue('#second', [browser.Keys.TAB])
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'third',
|
||||
'[constrained] Tabbing second element focuses the third.',
|
||||
);
|
||||
},
|
||||
)
|
||||
.setValue('#third', [browser.Keys.TAB])
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'first',
|
||||
'[constrained] Tabbing final element focuses the first.',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Confirm shift+tab on the first element focuses the third (final).
|
||||
browser
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
document.querySelector('#first').focus();
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'first',
|
||||
'[constrained] First element focused after calling focus().',
|
||||
);
|
||||
},
|
||||
)
|
||||
.setValue('#first', [browser.Keys.SHIFT, browser.Keys.TAB])
|
||||
.execute(
|
||||
// eslint-disable-next-line func-names, prefer-arrow-callback
|
||||
function () {
|
||||
return document.activeElement.id;
|
||||
},
|
||||
[],
|
||||
(result) => {
|
||||
browser.assert.equal(
|
||||
result.value,
|
||||
'third',
|
||||
'[constrained] Shift+tab the first element moves focus to the last element.',
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
browser.drupalLogAndEnd({ onlyOnError: false });
|
||||
},
|
||||
};
|
||||
50
web/core/tests/Drupal/Nightwatch/globals.js
Normal file
50
web/core/tests/Drupal/Nightwatch/globals.js
Normal file
@ -0,0 +1,50 @@
|
||||
const path = require('node:path');
|
||||
const fs = require('node:fs');
|
||||
const mkdirp = require('mkdirp');
|
||||
const nightwatchSettings = require('./nightwatch.conf');
|
||||
|
||||
const commandAsWebserver = (command) => {
|
||||
if (process.env.DRUPAL_TEST_WEBSERVER_USER) {
|
||||
return `sudo -u ${process.env.DRUPAL_TEST_WEBSERVER_USER} ${command}`;
|
||||
}
|
||||
return command;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
afterEach: (browser, done) => {
|
||||
// Writes the console log - used by the "logAndEnd" command.
|
||||
if (
|
||||
browser.drupalLogConsole &&
|
||||
(!browser.drupalLogConsoleOnlyOnError ||
|
||||
browser.currentTest.results.errors > 0 ||
|
||||
browser.currentTest.results.failed > 0)
|
||||
) {
|
||||
const resultPath = path.join(
|
||||
__dirname,
|
||||
`../../../${nightwatchSettings.output_folder}/consoleLogs/${browser.currentTest.module}`,
|
||||
);
|
||||
const status =
|
||||
browser.currentTest.results.errors > 0 ||
|
||||
browser.currentTest.results.failed > 0
|
||||
? 'FAILED'
|
||||
: 'PASSED';
|
||||
mkdirp.sync(resultPath);
|
||||
const now = new Date().toString().replace(/[\s]+/g, '-');
|
||||
const testName = (
|
||||
browser.currentTest.name || browser.currentTest.module
|
||||
).replace(/[\s/]+/g, '-');
|
||||
browser
|
||||
.getLog('browser', (logEntries) => {
|
||||
const browserLog = JSON.stringify(logEntries, null, ' ');
|
||||
fs.writeFileSync(
|
||||
`${resultPath}/${testName}_${status}_${now}_console.json`,
|
||||
browserLog,
|
||||
);
|
||||
})
|
||||
.end(done);
|
||||
} else {
|
||||
browser.end(done);
|
||||
}
|
||||
},
|
||||
commandAsWebserver,
|
||||
};
|
||||
115
web/core/tests/Drupal/Nightwatch/nightwatch.conf.js
Normal file
115
web/core/tests/Drupal/Nightwatch/nightwatch.conf.js
Normal file
@ -0,0 +1,115 @@
|
||||
// cspell:ignore testcases
|
||||
const path = require('node:path');
|
||||
const { globSync } = require('glob');
|
||||
|
||||
// Find directories which have Nightwatch tests in them.
|
||||
const regex = /(.*\/?tests\/?.*\/Nightwatch)\/.*/g;
|
||||
const collectedFolders = {
|
||||
Tests: [],
|
||||
Commands: [],
|
||||
Assertions: [],
|
||||
Pages: [],
|
||||
};
|
||||
const searchDirectory = process.env.DRUPAL_NIGHTWATCH_SEARCH_DIRECTORY || '';
|
||||
const defaultIgnore = ['vendor/**'];
|
||||
|
||||
globSync('**/tests/**/Nightwatch/**/*.js', {
|
||||
cwd: path.resolve(process.cwd(), `../${searchDirectory}`),
|
||||
follow: true,
|
||||
ignore: process.env.DRUPAL_NIGHTWATCH_IGNORE_DIRECTORIES
|
||||
? process.env.DRUPAL_NIGHTWATCH_IGNORE_DIRECTORIES.split(',').concat(
|
||||
defaultIgnore,
|
||||
)
|
||||
: defaultIgnore,
|
||||
})
|
||||
.sort()
|
||||
.forEach((file) => {
|
||||
let m = regex.exec(file);
|
||||
while (m !== null) {
|
||||
// This is necessary to avoid infinite loops with zero-width matches.
|
||||
if (m.index === regex.lastIndex) {
|
||||
regex.lastIndex += 1;
|
||||
}
|
||||
|
||||
const key = `../${m[1]}`;
|
||||
Object.keys(collectedFolders).forEach((folder) => {
|
||||
if (file.includes(`Nightwatch/${folder}`)) {
|
||||
collectedFolders[folder].push(`${searchDirectory}${key}/${folder}`);
|
||||
}
|
||||
});
|
||||
m = regex.exec(file);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove duplicate folders.
|
||||
Object.keys(collectedFolders).forEach((folder) => {
|
||||
collectedFolders[folder] = Array.from(new Set(collectedFolders[folder]));
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
src_folders: collectedFolders.Tests,
|
||||
output_folder: process.env.DRUPAL_NIGHTWATCH_OUTPUT,
|
||||
custom_commands_path: collectedFolders.Commands,
|
||||
custom_assertions_path: collectedFolders.Assertions,
|
||||
page_objects_path: collectedFolders.Pages,
|
||||
globals_path: 'globals.js',
|
||||
selenium: {
|
||||
start_process: false,
|
||||
},
|
||||
test_settings: {
|
||||
default: {
|
||||
globals: {
|
||||
defaultTheme: 'olivero',
|
||||
adminTheme: 'claro',
|
||||
},
|
||||
selenium_port: process.env.DRUPAL_TEST_WEBDRIVER_PORT,
|
||||
selenium_host: process.env.DRUPAL_TEST_WEBDRIVER_HOSTNAME,
|
||||
default_path_prefix: process.env.DRUPAL_TEST_WEBDRIVER_PATH_PREFIX || '',
|
||||
desiredCapabilities: {
|
||||
browserName: 'chrome',
|
||||
acceptSslCerts: true,
|
||||
'goog:chromeOptions': {
|
||||
w3c: !!process.env.DRUPAL_TEST_WEBDRIVER_W3C,
|
||||
args: process.env.DRUPAL_TEST_WEBDRIVER_CHROME_ARGS
|
||||
? process.env.DRUPAL_TEST_WEBDRIVER_CHROME_ARGS.split(' ')
|
||||
: [],
|
||||
},
|
||||
},
|
||||
screenshots: {
|
||||
enabled: true,
|
||||
on_failure: true,
|
||||
on_error: true,
|
||||
path: `${process.env.DRUPAL_NIGHTWATCH_OUTPUT}/screenshots`,
|
||||
},
|
||||
end_session_on_fail: false,
|
||||
skip_testcases_on_fail: false,
|
||||
},
|
||||
local: {
|
||||
webdriver: {
|
||||
start_process: process.env.DRUPAL_TEST_CHROMEDRIVER_AUTOSTART,
|
||||
port: process.env.DRUPAL_TEST_WEBDRIVER_PORT,
|
||||
cli_args: process.env.DRUPAL_TEST_WEBDRIVER_CLI_ARGS
|
||||
? process.env.DRUPAL_TEST_WEBDRIVER_CLI_ARGS.split(' ')
|
||||
: [],
|
||||
},
|
||||
desiredCapabilities: {
|
||||
browserName: 'chrome',
|
||||
acceptSslCerts: true,
|
||||
'goog:chromeOptions': {
|
||||
w3c: !!process.env.DRUPAL_TEST_WEBDRIVER_W3C,
|
||||
args: process.env.DRUPAL_TEST_WEBDRIVER_CHROME_ARGS
|
||||
? process.env.DRUPAL_TEST_WEBDRIVER_CHROME_ARGS.split(' ')
|
||||
: [],
|
||||
},
|
||||
},
|
||||
screenshots: {
|
||||
enabled: true,
|
||||
on_failure: true,
|
||||
on_error: true,
|
||||
path: `${process.env.DRUPAL_NIGHTWATCH_OUTPUT}/screenshots`,
|
||||
},
|
||||
end_session_on_fail: false,
|
||||
skip_testcases_on_fail: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user