Support from Acquia helps fund testing for Drupal Acquia logo

Comments

dawehner created an issue. See original summary.

samuel.mortenson’s picture

Status: Active » Needs review
FileSize
3.73 KB

Reposting patch from #2972229: Expand the JS test coverage using nightwatch:

👋I'm new to this but made an attempt to add a login, logout, and isLoggedIn command to our Nightwatch instance. Test coverage for the new commands are included.

Edit: We could also have a PHP script that returns an appropriate session cookie based on a user ID, then call that with child_process, if setting a username and password feels weird to people.

Edit 2: Another piece of self-review - the isLoggedIn command accepts a callback because the getCookies method is asynchronous, but the login and logout commands do not accept a callback even though they call isLoggedIn. Should they also take a callback? I researched this a little bit and it looks like with asynchronous _stuff_ in Nightwatch, you have to use callbacks, but can end up nesting many levels deep. Thoughts?

Status: Needs review » Needs work

The last submitted patch, 2: 2973879-2.patch, failed testing. View results

jibran’s picture

23:54:53 PHP Warning:  file_put_contents(/var/lib/drupalci/workspace/jenkins-drupal_patches-58861/source/core/.env): failed to open stream: Permission denied in /opt/drupalci/testrunner/src/DrupalCI/Plugin/BuildTask/BuildStep/Testing/NightwatchJS.php on line 62
23:54:53 sudo BABEL_DISABLE_CACHE=1 -u www-data yarn --cwd /var/www/html/core test:nightwatch
23:55:00 yarn run v1.6.0
23:55:00 $ cross-env BABEL_ENV=development node -r dotenv-safe/config -r babel-register ./node_modules/.bin/nightwatch --config ./tests/Drupal/Nightwatch/nightwatch.conf.js
23:55:00 
23:55:00 /var/www/html/core/node_modules/dotenv-safe/index.js:31
23:55:00             throw new MissingEnvVarsError(allowEmptyValues, options.path || '.env', example, missing, dotenvResult.error);
23:55:00             ^
23:55:00 MissingEnvVarsError: The following variables were defined in .env.example but are not present in the environment:
23:55:00   DRUPAL_TEST_BASE_URL, DRUPAL_TEST_DB_URL, DRUPAL_TEST_WEBDRIVER_HOSTNAME, DRUPAL_TEST_WEBDRIVER_PORT, DRUPAL_TEST_CHROMEDRIVER_AUTOSTART, DRUPAL_NIGHTWATCH_OUTPUT, DRUPAL_NIGHTWATCH_IGNORE_DIRECTORIES
23:55:00 Make sure to add them to .env or directly to the environment.
23:55:00 
23:55:00 If you expect any of these variables to be empty, you can use the allowEmptyValues option:
23:55:00 require('dotenv-safe').config({
23:55:00   allowEmptyValues: true
23:55:00 });
23:55:00 
23:55:00 Also, the following error was thrown when trying to read variables from  .env:
23:55:00 ENOENT: no such file or directory, open '/var/www/html/core/.env'
23:55:00     at Object.config (/var/www/html/core/node_modules/dotenv-safe/index.js:31:19)
23:55:00     at /var/www/html/core/node_modules/dotenv-safe/config.js:9:18
23:55:00     at Object.<anonymous> (/var/www/html/core/node_modules/dotenv-safe/config.js:10:3)
23:55:00     at Module._compile (module.js:652:30)
23:55:00     at Object.Module._extensions..js (module.js:663:10)
23:55:00     at Module.load (module.js:565:32)
23:55:00     at tryModuleLoad (module.js:505:12)
23:55:00     at Function.Module._load (module.js:497:3)
23:55:00     at Module.require (module.js:596:17)
23:55:00     at Function.Module._preloadModules (module.js:753:12)
23:55:00 error Command failed with exit code 1.

hmmm

GrandmaGlassesRopeMan’s picture

This failed due to a permissions issue on the test bot,

23:54:53 PHP Warning:  file_put_contents(/var/lib/drupalci/workspace/jenkins-drupal_patches-58861/source/core/.env): failed to open stream: Permission denied in /opt/drupalci/testrunner/src/DrupalCI/Plugin/BuildTask/BuildStep/Testing/NightwatchJS.php on line 62
dawehner’s picture

Let's prefix these commands with Drupal :)

  1. +++ b/core/tests/Drupal/Nightwatch/Commands/isLoggedIn.js
    @@ -0,0 +1,22 @@
    +exports.command = function isLoggedIn(callback) {
    

    Should we prefix this command with "userIsLoggedIn"?

  2. +++ b/core/tests/Drupal/Nightwatch/Commands/isLoggedIn.js
    @@ -0,0 +1,22 @@
    +    let sessionExists = false;
    +    cookies.value.forEach((cookie) => {
    +      if (cookie.name.match(/^SESS/)) {
    +        sessionExists = true;
    +      }
    +    });
    

    Nitpick: We could have used Array.prototype.some which stops early.

  3. +++ b/core/tests/Drupal/Nightwatch/Commands/login.js
    @@ -0,0 +1,30 @@
    +  this.isLoggedIn((sessionExists) => {
    +    // Log the current user out if necessary.
    +    if (sessionExists) {
    +      this.logout();
    +    }
    

    Nice! It does the same as BTB

  4. +++ b/core/tests/Drupal/Nightwatch/Tests/loginTest.js
    @@ -0,0 +1,19 @@
    +  'Test login': (browser) => {
    +    browser.login('test', 'test');
    +  },
    +  'Test logout': (browser) => {
    +    browser
    +      .login('test', 'test')
    +      .logout();
    

    I'm curious whether we should have some additional assertions here? I guess we are fine actually.

samuel.mortenson’s picture

Status: Needs work » Needs review
FileSize
3.84 KB
4.88 KB

Addressed #6. Additional assertions should not be needed, although the test does looks funny without them. :-)

Putting back into needs review, but would like someone else to look at my "Edit 2" comment from #2. I also now wonder if "getCookies" is actually asyncronous, or if it just happens to take a callback.

dawehner’s picture

+++ b/core/tests/Drupal/Nightwatch/Commands/drupalUserIsLoggedIn.js
@@ -0,0 +1,20 @@
+  this.getCookies((cookies) => {
+    if (typeof callback === 'function') {
+      const sessionExists = cookies.value.some((cookie) => {
+        return cookie.name.match(/^SESS/);
+      });
+
+      callback.call(this, sessionExists);

I tried to look at the sourcecode of nightwatch, and well, I couldn't 100% figure that out to be honest. One thing though which confuses me here: Is this helpful at all when callback is not a function? If it isn't this function wouldn't do anything.

samuel.mortenson’s picture

@dawehner I guess I was just trying to avoid runtime errors, but we could remove the check so users could at least see an error (ex: Uncaught TypeError callback.call is not a function).

webchick’s picture

Priority: Normal » Major
Issue tags: +JavaScript, +Javascript Modernization Initiative

According to @lauriii this is a blocker for the JS Nightwatch testing stuff.

jibran’s picture

Status: Needs review » Needs work

User login is useless as it is if we don't allow passing the roles or permissions while creating the user.

dawehner’s picture

+++ b/core/tests/Drupal/Nightwatch/Commands/drupalUserIsLoggedIn.js
@@ -0,0 +1,20 @@
+    if (typeof callback === 'function') {
+      const sessionExists = cookies.value.some((cookie) => {
+        return cookie.name.match(/^SESS/);
+      });
+
+      callback.call(this, sessionExists);
+    }

What would be the point of making the callable optional?

jibran’s picture

@lauriii just pointed me to #2974619: Create nightwatch command to install modules I think creating a console command to create the user with given role/permissions is the way to fix #11.

lauriii’s picture

Yesterday @Wim Leers mentioned on a call that we could use #2403307: RPC endpoints for user authentication: log in, check login status, log out for logging in, instead of using the login form.

dawehner’s picture

Nightwatch

@justafish, @alexpott, @lauriii, @drpal had a discussion about nightwatch tests at Drupal dev days in a room without real sunlight.

Goals

  • We want to unblock people writing tests
  • We want to avoid having to reinvent drush or command line tools
  • We want to avoid building our own test API

Result

  • Create an user with admin role by default
  • Login the user by default
  • For any interaction which needs to create stuff like enabling modules, creating content types etc.
    we use a browser, like moving to /admin/modules check the checkbox for a module and hit save
  • This way we decouple ourselves from the API
  • We will create new commands for all this setup functionality
  • Optimizing for test performance is something we can worry later, if needed.
jibran’s picture

I still think that generic console commands i.e. creating a user and installing module would be really helpful but these goals do make sense to me.

For any interaction which needs to create stuff like enabling modules, creating content types etc.
we use a browser, like moving to /admin/modules check the checkbox for a module and hit save

We'd need a user 1 for that, right?

We will create new commands for all this setup functionality

You mean nightwatch commands not symfony console commands?

Optimizing for test performance is something we can worry later, if needed.

We can always run JS and PHP tests in parallel.

Wim Leers’s picture

Login the user by default
[…]
This way we decouple ourselves from the API

+1, this seems like a great initial direction.

lauriii’s picture

Status: Needs work » Needs review
FileSize
14.75 KB
14.43 KB

Created a new patch based on #15.

dawehner’s picture

Great progress here!

  1. +++ b/core/tests/Drupal/Nightwatch/Commands/drupalCreateRole.js
    @@ -0,0 +1,55 @@
    +            permissions.map(permission => {
    +              return new Promise(resolve => {
    +                client.click(`input[name="${machineName}[${permission}]"]`, () => {
    +                  resolve();
    +                });
    +              });
    +            })
    

    Should we use utils.promisify here instead?

  2. +++ b/core/tests/Drupal/Nightwatch/Commands/drupalCreateUser.js
    @@ -0,0 +1,55 @@
    +        .saveScreenshot('/Users/lauri.eskola/user_form.png')
    

    Let's drop that :)

  3. +++ b/core/tests/Drupal/Nightwatch/Commands/drupalInstall.js
    @@ -38,12 +39,9 @@ exports.command = function drupalInstall({ setupFile = '' }, callback) {
    -  // 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;
    

    How is this not longer a problem?

  4. +++ b/core/tests/Drupal/Nightwatch/Tests/loginTest.js
    @@ -0,0 +1,24 @@
    +      .drupalRelativeURL('/admin/reports')
    +      .saveScreenshot('/Users/lauri.eskola/status_report.png');
    

    Let's have some assertions here

  5. +++ b/core/tests/Drupal/TestSite/Commands/TestSiteUserLoginCommand.php
    @@ -0,0 +1,87 @@
    +      ->addArgument('uid', InputArgument::OPTIONAL, 'The id of the user to which the link will be generated', 1)
    ...
    +    $userEntity = $container->get('entity_type.manager')
    +      ->getStorage('user')
    +      ->load($input->getArgument('uid'));
    

    When the argument is optional we should either have some form of default value, or do some validation

GrandmaGlassesRopeMan’s picture

#19.1 - this gets a bit confusing, i gave it a shot but couldn't get it working.
#19.2 - fixed
#19.3 - fixed
#19.4 - fixed
#19.5 - php help please.

jibran’s picture

  1. Fixed #19.5.
  2. +++ b/core/tests/Drupal/Nightwatch/Commands/drupalCreateRole.js
    @@ -0,0 +1,57 @@
    +        .expect.element('.user-role-form .machine-name-value').to.be.visible.before(2000);
    ...
    +      this.perform((done) => {
    

    Added a comment to explain it.

  3. Fixed some CS issues.
GrandmaGlassesRopeMan’s picture

Status: Needs review » Needs work
+++ b/core/tests/Drupal/Nightwatch/Commands/drupalCreateRole.js
@@ -22,6 +22,8 @@ exports.command = function drupalCreateRole({ permissions, name = null }, callba
+        /* Wait for the machine name to appear so that it can be used later to
+         select the permissions from the permission page. */

This comment format should be like,

/**

*/
jibran’s picture

Status: Needs work » Needs review
FileSize
922 bytes
15.21 KB

Thanks, for the review. I follow the core/tests/Drupal/Nightwatch/Commands/drupalUninstall.js comment pattern.

dawehner’s picture

Status: Needs review » Needs work
+++ b/core/tests/Drupal/TestSite/Commands/TestSiteUserLoginCommand.php
@@ -0,0 +1,88 @@
+    $uid = $input->getArgument('uid');
+    if (!is_numeric($uid)) {
+      throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
+    }

If we check for this, we should actually provide a helpful error message: 'The "%s" argument needs to be a number, but it is %s'. Also $name doesn't exist.

lauriii’s picture

Status: Needs work » Needs review
FileSize
15.23 KB
838 bytes

Fixes #24.

dawehner’s picture

Thank you @lauriii
I'm wondering whether it would be sensible to have some level of test coverage here as well.

jibran’s picture

Assigned: Unassigned » jibran

> I'm wondering whether it would be sensible to have some level of test coverage here as well.

Going to write a PHP test for the console command.

Wim Leers’s picture

Status: Needs review » Needs work
  1. +++ b/core/tests/Drupal/TestSite/Commands/TestSiteUserLoginCommand.php
    @@ -0,0 +1,88 @@
    +  /**
    +   * The database prefix of this test run.
    +   *
    +   * @var string
    +   */
    +  protected $databasePrefix;
    

    AFAICT We don't need this. This seems to be copy/pasted from TestSiteInstallCommand, which is a special case.

  2. +++ b/core/tests/Drupal/TestSite/Commands/TestSiteUserLoginCommand.php
    @@ -0,0 +1,88 @@
    +  /**
    +   * Time limit in seconds for the test.
    +   *
    +   * Used by \Drupal\Core\Test\FunctionalTestSetupTrait::prepareEnvironment().
    +   *
    +   * @var int
    +   */
    +  protected $timeLimit = 500;
    

    Same.

  3. +++ b/core/tests/Drupal/TestSite/Commands/TestSiteUserLoginCommand.php
    @@ -0,0 +1,88 @@
    +      ->addArgument('uid', InputArgument::OPTIONAL, 'The id of the user to which the link will be generated', 1)
    

    s/id/ID/
    s/to/for/
    s/which/whom/

    Also, why isn't this required?

  4. +++ b/core/tests/Drupal/TestSite/Commands/TestSiteUserLoginCommand.php
    @@ -0,0 +1,88 @@
    +      throw new InvalidArgumentException(sprintf('The "uid" argument needs to be a number, but it is "%s".', $uid));
    

    s/a number/an integer/

Wim Leers’s picture

--- /dev/null
+++ b/core/tests/Drupal/TestSite/Commands/TestSiteUserLoginCommand.php

This only does the login bit from the issue title.

Either the patch still needs a logout command too, or the issue title and summary need to be updated.

lauriii’s picture

This only does the login bit from the issue title.

We don't need PHP command for logging out. There's a Nightwatch command for doing that. 🙌

Wim Leers’s picture

Wim Leers’s picture

Issue tags: +Needs change record

Also.

lauriii’s picture

Title: Add login/logout commands to nightwatch » Add login/logout Nightwatch commands
Issue tags: -Needs title update, -Needs issue summary update
jibran’s picture

Assigned: jibran » Unassigned
Status: Needs work » Needs review
FileSize
12.35 KB
25.54 KB
core/tests/Drupal/Nightwatch/Commands/drupalLogin.js
core/tests/Drupal/Nightwatch/Commands/drupalLogout.js

Fixed #28 and #26.

Wim Leers’s picture

Title: Add login/logout Nightwatch commands » Add login/logout Nightwatch commands (and a Drupal "login" command to allow for that)

Talked to @lauriii, I didn't realize there were both Nightwatch and Drupal commands (because I was asked to review the PHP side). I think this issue title covers it all.

Wim Leers’s picture

  1. +++ b/core/tests/Drupal/TestSite/Commands/TestSiteUserLoginCommand.php
    @@ -26,29 +26,13 @@ class TestSiteUserLoginCommand extends Command {
    +      ->addArgument('uid', InputArgument::OPTIONAL, 'The ID of the user for whom the link will be generated', 1)
    

    This is still optional. Why?

  2. +++ b/core/tests/Drupal/Tests/Scripts/TestSiteApplicationTest.php
    @@ -254,6 +250,50 @@ public function testTearDownDbPrefixValidation() {
    +    $this->assertSame(0, $process->getExitCode());
    ...
    +    $this->assertSame(1, $process->getExitCode());
    

    Shouldn't we also assert the output?

    And in the latter case: shouldn't we perform a HTTP request to the given URL and verify that the response contains a Set-Cookie header with the Drupal session cookie?

GrandmaGlassesRopeMan’s picture

- fixes linting and jsdoc issues.

martin107’s picture

... coming from the meeting in slack... where there was a call for review.

FWIW after a slow visual scan of the patch all the code patterns employed here...look straight out of the text book...

nothing that leaps out as questionable.

My feeling - A good addition to the project

So +1 from me.

lauriii’s picture

Addresses #36.

jibran’s picture

+++ b/core/tests/Drupal/TestSite/Commands/TestSiteInstallCommand.php
@@ -102,6 +102,7 @@ protected function execute(InputInterface $input, OutputInterface $output) {
+        'site_path' => $this->siteDirectory,

+++ b/core/tests/Drupal/Tests/Scripts/TestSiteApplicationTest.php
@@ -254,6 +250,61 @@ public function testTearDownDbPrefixValidation() {
+    $this->assertSame('sites/simpletest/' . str_replace('test', '', $db_prefix), $site_path);

FWIW, this assert is checking the change in install command.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community
jibran’s picture

Writing a change record.

jibran’s picture

Issue tags: -Needs change record

Created the following change record https://www.drupal.org/node/2986276. Feel free to improve it.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 0083ba97d7 to 8.7.x and c5782b95a2 to 8.6.x. Thanks!

  • alexpott committed 0083ba9 on 8.7.x
    Issue #2973879 by jibran, lauriii, drpal, samuel.mortenson, dawehner,...

  • alexpott committed c5782b9 on 8.6.x
    Issue #2973879 by jibran, lauriii, drpal, samuel.mortenson, dawehner,...
jibran’s picture

Published the change record as well. Thanks! for the commit.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

Gábor Hojtsy’s picture

Issue tags: +8.6.0 highlights