Extensions & Integrations
Nightwatch Tools
apostrophe-nightwatch-tools
This module supplies Nightwatch custom commands and executable test steps which are useful when working with apostrophecms.
Without these tools it is very difficult to achieve truly stable Nightwatch tests for an Apostrophe site. With them, it's pretty easy.
These tools are used by the apostrophe-enterprise-testbed project, an Apostrophe testbed site that confirms new changes in the major Apostrophe modules have not caused any regressions. You can use them to create tests for your own site by using that project as a model.
Again, reviewing the code of that project is strongly recommended before proceeding.
Installation
These instructions assume you have created nightwatch tests in the past, and that your project already depends on nightwatch. For a complete, working example see the apostrophe-enterprise-testbed project.
Install the module:
npm install --dev apostrophe-nightwatch-tools
Now, in your nightwatch.js
configuration file, set custom_commands_path
to an array containing the commands
folder of this module, plus any custom command folders of your own:
custom_commands_path: [
"node_modules/apostrophe-nightwatch-tools/commands",
"tests/commands"
],
Then structure your actual test scenario .js
files like this. Note the use of require
to bring in files that live in a subdirectory of the module.
// tests/scenarios/article.spec.js
const server = require('apostrophe-nightwatch-tools/server');
const steps = require('apostrophe-nightwatch-tools/steps');
module.exports = Object.assign(
{
before: (client, done) => {
console.log(process.argv);
console.log('IN START');
client.resizeWindow(1200, 800);
if (!this._server) {
this._server = server.create('localhost', 3111);
this._server.start(done);
}
},
after: (client, done) => {
console.log('IN AFTER');
client.end(() => {
console.log('STOPPING FROM AFTER');
this._server.stop(done);
});
},
},
// Execute various steps found in the module
steps.main(),
steps.login(),
steps.switchLocale('en'),
steps.switchToDraftMode(),
steps.createArticle('New Article Title'),
// Execute a custom step
{
'submit the article, via the "Workflow" menu in the dialog box': (client) => {
const manageTableRowSelector = 'table[data-items] tr[data-piece]:first-child';
const editArticleBtnSelector = `${manageTableRowSelector} .apos-manage-apostrophe-blog-title a`;
const workflowModalBtnSelector =
`[data-apos-dropdown-name="workflow"]`;
const submitWorkflowBtnSelector = `[data-apos-workflow-submit]`;
// Wait until a modal of the specified type is
// the current modal, then click the button to edit the first article
client.clickInModal('apostrophe-blog-manager-modal', editArticleBtnSelector);
client.clickInModal('apostrophe-blog-editor-modal', workflowModalBtnSelector);
client.clickInModal('apostrophe-blog-editor-modal', submitWorkflowBtnSelector);
// Wait until a modal of the specified type is the current modal
client.waitForModal('apostrophe-blog-manager-modal');
}
}
);
Commands
The commands in the commands/
folder cover very frequent operations like clicking an element in a specific modal, after first waiting to ensure Apostrophe is not in a busy state and that modal is actually the current modal. Here is a reference guide:
waitForModal
waitForModal('apostrophe-blog-editor-modal')
will wait until a modal of that type (a blog article editor modal) is the current modal. For pieces, the type name you are looking for is usually the name
option of your pieces module, followed by -editor-modal
(editing one), -manager-modal
(managing many), or -widget-editor
(editing the widget corresponding to that piece type). You can inspect the browser to discover the right type name; it will be in the data-apos-modal-current
attribute.
This command also waits until Apostrophe is not busy, i.e. .apos-global-busy
and .apos-busy
do not appear in the DOM. This prevents tests from failing due to a variable amount of time spent carrying out an action.
clickInModal
clickInModal('apostrophe-blog-editor-modal', '[data-apos-save])
will wait until a modal of that type (a blog article editor modal) is the current modal, then click the save button. See waitForModal
for details on the modal type name. The selector can be any CSS selector. Note that CSS does not include all jQuery selectors, for instance :first
is not valid CSS, only valid jQuery.
This command also waits until Apostrophe is not busy, i.e. .apos-global-busy
and .apos-busy
do not appear in the DOM. This prevents tests from failing due to a variable amount of time spent carrying out an action.
clickWhenReady
clickWhenReady('.my-custom-button')
will click on the matching element after first ensuring that it is ready (it is visible and Apostrophe is not in a busy state).
waitForElementReady
waitForElementReady('.my-custom-button')
will wait until the specified element is ready (it is visible and Apostrophe is not in a busy state).
openAdminBarItem
openAdminBarItem('apostrophe-blog')
will trigger the admin bar button for the apostrophe-blog
module, opening the "manage" modal for that type of content. This method can open both grouped and ungrouped admin bar items.
resetValueInModal
resetValueInModal('apostrophe-blog-editor-modal', '[name="title"]', 'New Title')
will wait until the appropriate modal is active (see waitForModal
), select the title field within that context, and replace its current text with New Title
.
waitForNoModals
waitForNoModals()
will wait until no Apostrophe modals are active. It is appropriate when returning to the page context after working with modals in your tests.
categoryScreenshot
categoryScreenshot('article.png')
will take a screenshot and store it to screenshots/latest/article.png
, relative to the current working directory. The word "latest" may be replaced by setting the environment variable VISUAL_CATEGORY. Creates missing folders if needed. Used by the apostrophe-enterprise-testbed
project to set up previous
with snapshots based on the latest npm releases, and latest
with snapshots of the latest git masters.
Standalone test steps
The steps in the steps/
folder cover standalone tasks like logging in or committing modified content. These can be included in a Nightwatch test scenario as shown above.
These steps include appropriate assertions and will fail if they do not carry out the expected action successfully.
addTextWidgetTo
steps.addTextWidgetTo({selector: '.footer', text: 'Rich Text Widget line global'})
will add a rich text widget to the first area located inside the first element matching the given selector. The rich text widget will have the HTML text specified by text
.
changePageTypeTo
steps.changePageTypeTo(type)
will open the page settings modal, change the page type to the specified type, and save page settings.
checkNotification
steps.checkNotification('message')
will check for a notification div containing exactly the specified text.
checkSubmitted
steps.checkSubmitted([ 'Title 1', 'Title 2' ])
will open the workflow modal and verify that all of the specified titles have been submitted via the "Submit" button.
commit
steps.commit()
will click the commit button on the page, commit one document, and skip exporting it. steps.commit(2)
will commit two documents. The step will fail if this does not return the browser to a state with no modals (the commit sequence is finished).
commitAndExport
See steps.commit()
above. However this method also exports the content to all locales by clicking the checkbox for the locale with the name attribute master
. There must be a locale with the name master
for this to work.
confirm200ByRelativeUrl
steps.confirm200ByRelativeUrl('/test')
will fetch the given URL, relative to the active page, and succeed only if the status code is 200. This does not navigate the browser away from the current page. The active session and cookies are not included. For advanced cases, fully navigate the test browser.
confirm404ByRelativeUrl
steps.confirm4094ByRelativeUrl('/test')
will fetch the given URL, relative to the active page, and succeed only if the status code is 404. This does not navigate the browser away from the current page. The active session and cookies are not included. For advanced cases, fully navigate the test browser.
createArticle
steps.createArticle('My Article')
creates a new blog post (apostrophe-blog
) with the given title. The article is published, with a publication date of today, but is not committed or exported at this stage. At the end of the step, the "manage blog posts" modal is still open.
login
steps.login()
attempts to log in with the username admin
and the password demo
.
main
steps.main()
navigates to the home page and verifies it has done so. It is frequently the first step. This step will fail if the home page does not have the home-page
body class, however see also navigateToHome
.
makeIncognitoRequestByRelativeUrl
An example is easiest for this step:
steps.makeIncognitoRequestByRelativeUrl('/', (client, $) => {
const richTextSelector = '.demo-main [data-rich-text]';
client.assert.equal($(richTextSelector).length, 0);
})
This step fetches the specified URL, without the session and cookies of the current user, and returns a cheerio
object (a jQuery
-like object) which can be used to check the contents of the reply. The test browser does not navigate to a new location. If you need more complete access to the logged-out experience, you should actually log the test user out.
makeSubPage
steps.makeSubPage('Regression test')
creates a page with the specified title
and the type default
, using the context menu and the page settings modal. Note that this constitutes a good test of the basic operation of that modal.
navigateToHome
steps.navigateToHome()
navigates to the homepage. Unlike main()
it does not look for a home-page
body class.
navigateToRelativeUrl
steps.navigateToRelativeUrl('/')
navigates to the given URL and pauses until the browser is ready.
navigateToRelativeUrlAndconfirm200
steps.navigateToRelativeURlAndconfirm200('/')
navigates to the given URL and confirms a 200
status code. There is no pause.
navigateToRelativeUrlAndconfirm404
steps.navigateToRelativeURlAndconfirm200('/')
navigates to the given URL and confirms a 404
status code. There is no pause.
openContextMenu
steps.openContextMenu('Page Settings')
opens the context menu (the one in the lower left corner), clicks a button with the specified text in its label, and waits for a modal to open.
submitChanges
steps.submitChanges()
clicks the Submit
button on the page, waits for the notification to appear and disappear, and then waits for the label to change to Submitted
.
switchLocale
steps.switchLocale('es')
switches to the specified locale.
switchToDraftMode
steps.switchToDraftMode()
switches to draft mode.
switchToLiveMode
steps.switchToLiveMode()
switches to draft mode.
workflowCommitArticle
Assumes that the apostrophe-blog manager modal is already open. steps.workflowCommitArticle()
commits the first article listed in the apostrophe-blog manager modal. The article is not exported.
workflowSubmitArticle
Assumes that the apostrophe-blog manager modal is already open. steps.workflowSubmitArticle()
submits the first article listed in the apostrophe-blog manager modal, via the workflow dropdown in the blog article editor modal.
server.js
server.js
is a utility file that exports conveniences for creating an Apostrophe object that listens on the appropriate port, starting up Nightwatch with the chrome driver, and making sure that any previous Apostrophe objects bound to the same port are definitely gone before launching the next one for a new scenario. Its use is entirely optional. See the example above, as well as the apostrophe-enterprise-testbed project, for a good guide to its use.
Changelog
3.3.3 - 2020-08-26:
- Respect the APOS_MONGODB_URI environment variable with respect to establishing a connection, so tests can be run with an external database.
3.3.2 - 2020-07-15:
- pause for asynchronous save operation before submitting, not after.
3.3.1 - 2020-06-17:
- pause when committing and exporting, to allow asynchronous page content save operations to complete. Increases test stability.
3.3.0 - 2020-06-17:
- pause when committing and submitting, to allow asynchronous page content save operations to complete. Increases test stability.
steps.login
can now be called with a username and password. Otherwise it still defaults toadmin
anddemo
.
3.2.0: Windows-compatible. Thanks to Amin Shazrin.
3.1.0: login step now expects to see draft-page, not live-page, because the latest version of apostrophe-workflow defaults to draft mode. Upgrade both modules.
3.0.2: small pause in clickInModal to avoid unpredictable failures.
3.0.1: small pause in login step to avoid unpredictable failures.
3.0.0: Must be used with nightwatch
1.x, not 0.x. Tests pass more reliably.
2.1.0: changeWidgetPersona.js
: a new step added for changing the persona of a widget.
2.0.10: createArticle.js
step no longer focuses on the "Basics" tab when setting the publication date field value, as it now lives in the "Meta" group.
2.0.9: the addTextWidgetTo.js
step now more accurately targets the specific instance of CKEditor when setting the data. This is useful when you have multiple CKEditor rich text instances on a single page.
2.0.8: in the createArticle.js
step, an additional task was added to make sure the "Basics" tab was focused on before creating an article title.
2.0.7: do not print unnecessary echo of server output.
2.0.6: return result of synchronous task
method.
2.0.5: more race condition elimination relating to the admin bar.
2.0.4: fixed issue where the switchLocales
task failed since the introduction of button
tags for the locale switcher in the context area. Chose to use the one in the admin bar, but launch it properly.
2.0.3: added missing dependencies to package.json
. Removed node_modules
from git.
2.0.2: introduced categoryScreenshot
command.