Repository: jsperf/jsperf.com
Pull request #319 (2017-01-15 03:50:18)

add e2e test to login and create page
I created @1xTEST to be able to authenticate with GitHub without disabling my own 2FA.

By being able to run e2e locally to create pages this could satisfy #24 and #86 

- [x] login
- [x] create page
- [x] browse to page


Patch:
diff --git a/.gitignore b/.gitignore
index 52e59a3..7c181ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ node_modules
 coverage.html
 lcov.info
 *.log
+test/e2e/screenshots/
diff --git a/.travis.yml b/.travis.yml
index a100dd8..b29e195 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,7 +10,7 @@ before_install:
   - mysql -uroot -e "CREATE DATABASE jsperf; GRANT ALL ON jsperf.* TO 'jsuser'@'localhost' IDENTIFIED BY 'jspass'; FLUSH PRIVILEGES;"
 
 env:
-  - NODE_ENV=test PORT=3000 MYSQL_USER=jsuser MYSQL_PASSWORD=jspass MYSQL_DATABASE=jsperf BROWSERSCOPE=encrypt_me_later GITHUB_CLIENT_ID=encrypt_me_later GITHUB_CLIENT_SECRET=encrypt_me_later GITHUB_CALLBACK=http://localhost:3000 BELL_COOKIE_PASS=password-should-be-32-characters COOKIE_PASS=password-should-be-32-characters
+  - NODE_ENV=test PORT=3000 MYSQL_USER=jsuser MYSQL_PASSWORD=jspass MYSQL_DATABASE=jsperf GITHUB_CALLBACK=http://localhost:3000 BELL_COOKIE_PASS=password-should-be-32-characters COOKIE_PASS=password-should-be-32-characters
 
 script:
   - npm test
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ab2024..37f5833 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -32,7 +32,7 @@ _If you are missing code coverage, open `coverage.html` in the root of the proje
 
 End-to-end testing is done by [Selenium Webdriver](https://www.npmjs.com/package/selenium-webdriver). [SauceLabs](https://saucelabs.com) provides Selenium infrastructure for CI. To run tests locally, you'll need [Chrome](https://www.google.com/chrome) and [webdriver-manager](https://www.npmjs.com/package/webdriver-manager).
 
-```bash
+```
 npm i -g webdriver-manager
 
 webdriver-manager update
@@ -40,8 +40,8 @@ webdriver-manager update
 
 In one terminal, have jsPerf running (`npm start`). In another, have webdriver-manager running (`webdriver-manager start`). And in yet another, run the tests:
 
-```bash
-npm run test-e2e
+```
+SELENIUM_SERVER=http://127.0.0.1:4444/wd/hub npm run test-e2e
 ```
 
 ## Adding new dependencies
diff --git a/manifest.js b/manifest.js
index 1da8061..6b19013 100644
--- a/manifest.js
+++ b/manifest.js
@@ -94,7 +94,6 @@ var manifest = {
         register: 'yar',
         options: {
           cookieOptions: {
-            // name: 'jsPerf', FIXME
             password: config.get('/auth/session/pass'),
             isSecure: config.get('/auth/session/secure'),
             isHttpOnly: true
@@ -132,7 +131,7 @@ var manifest = {
           user: config.get('/mysql/user'),
           pass: config.get('/mysql/pass'),
           db: config.get('/mysql/db'),
-          debug: config.get('/debug')
+          debug: criteria.env !== 'production'
         }
       }
     },
diff --git a/package.json b/package.json
index c544e01..81e8e4a 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,7 @@
     "lint": "lab -d -L",
     "start": "node server.js",
     "test": "lab -t 100 -c -L -r lcov -o lcov.info -r console -o stdout -r html -o coverage.html test/unit",
-    "test-e2e": "lab -L -m 60000 -M 60000 test/e2e",
+    "test-e2e": "lab -L -m 30000 -M 10000 test/e2e",
     "snyk-protect": "snyk protect",
     "deploy": "now --public -e NODE_ENV=production -e SCHEME=https -e DOMAIN=jsperf.com -e GITHUB_CALLBACK=https://jsperf.com -e MYSQL_USER=@jsperf-db-user -e MYSQL_PASSWORD=@jsperf-db-pass -e MYSQL_DATABASE=@jsperf-db-db -e MYSQL_HOST=@jsperf-db-host -e MYSQL_PORT=@jsperf-db-port -e LOGGLY_TOKEN=@jsperf-loggly-token -e LOGGLY_SUBDOMAIN=@jsperf-loggly-subdomain -e GITHUB_CLIENT_ID=@jsperf-github-id -e GITHUB_CLIENT_SECRET=@jsperf-github-secret -e BROWSERSCOPE=@jsperf-browserscope -e BELL_COOKIE_PASS=@jsperf-bell-pass -e COOKIE_PASS=@jsperf-cookie-pass",
     "watch": "nodemon -e js,hbs server.js"
diff --git a/server/repositories/pages.js b/server/repositories/pages.js
index 05257ad..05e93fd 100644
--- a/server/repositories/pages.js
+++ b/server/repositories/pages.js
@@ -101,16 +101,16 @@ exports.register = function (server, options, next) {
     //   LIMIT 0, 50
     // )
     // y LEFT JOIN (
-    //   SELECT t.pageid, COUNT(t.pageid) AS testCount
+    //   SELECT t.pageID, COUNT(t.pageID) AS testCount
     //   FROM tests t
-    //   GROUP BY t.pageid
+    //   GROUP BY t.pageID
     // )
-    // z ON z.pageid = y.pID';
+    // z ON z.pageID = y.pID';
 
     var q = '%' + searchTerms + '%';
 
     return db.genericQuery(
-      'SELECT * FROM (SELECT x.id AS pID, x.slug AS url, x.revision, x.title, x.published, x.updated, COUNT(x.slug) AS revisionCount FROM pages x WHERE x.title LIKE ? OR x.info LIKE ? GROUP BY x.slug ORDER BY updated DESC LIMIT 0, 50) y LEFT JOIN (SELECT t.pageid, COUNT(t.pageid) AS testCount FROM tests t GROUP BY t.pageid) z ON z.pageid = y.pID;',
+      'SELECT * FROM (SELECT x.id AS pID, x.slug AS url, x.revision, x.title, x.published, x.updated, COUNT(x.slug) AS revisionCount FROM pages x WHERE x.title LIKE ? OR x.info LIKE ? GROUP BY x.slug ORDER BY updated DESC LIMIT 0, 50) y LEFT JOIN (SELECT t.pageID, COUNT(t.pageID) AS testCount FROM tests t GROUP BY t.pageID) z ON z.pageID = y.pID;',
       [q, q]
     );
   });


Test patch:
diff --git a/test/e2e/_helper.js b/test/e2e/_helper.js
index cfe713c..bed9547 100644
--- a/test/e2e/_helper.js
+++ b/test/e2e/_helper.js
@@ -1,5 +1,10 @@
+const fs = require('fs');
+const path = require('path');
 const Webdriver = require('selenium-webdriver');
 
+/*
+saucelabs - report status for jobs from CI
+*/
 let saucelabs;
 const username = process.env.SAUCE_USERNAME;
 const accessKey = process.env.SAUCE_ACCESS_KEY;
@@ -20,8 +25,14 @@ if (process.env.SELENIUM_SERVER) {
 
 exports.saucelabs = saucelabs;
 
+/*
+JSPERF_HOST - standardized host for all tests to use in `get`
+*/
 exports.JSPERF_HOST = process.env.JSPERF_HOST || 'http://localhost:3000';
 
+/*
+build - returns new web driver
+*/
 exports.build = function () {
   return new Webdriver.Builder()
     .withCapabilities({
@@ -34,3 +45,17 @@ exports.build = function () {
     .usingServer(process.env.SELENIUM_SERVER)
     .build();
 };
+
+/*
+screenshot - takes and stores to `test/e2e/screenshots`
+*/
+exports.screenshot = function (driver, pathname) {
+  return driver.takeScreenshot()
+  .then((data) => {
+    fs.writeFileSync(
+      path.resolve(__dirname, 'screenshots', Date.now() + '-' + pathname + '.png'),
+      data,
+      { encoding: 'base64' }
+    );
+  });
+};
diff --git a/test/e2e/home.js b/test/e2e/home.js
new file mode 100644
index 0000000..23df160
--- /dev/null
+++ b/test/e2e/home.js
@@ -0,0 +1,158 @@
+const Lab = require('lab');
+const Code = require('code');
+const Helper = require('./_helper');
+
+const lab = exports.lab = Lab.script();
+
+lab.experiment('Home page', () => {
+  let driver;
+  let sessionID;
+  let passed;
+
+  lab.beforeEach(() => {
+    passed = false;
+    driver = Helper.build();
+
+    return driver.getSession()
+    .then((session) => session.getId())
+    .then((id) => {
+      sessionID = id;
+    });
+  });
+
+  lab.afterEach((done) => {
+    driver.quit();
+
+    Helper.saucelabs.updateJob(sessionID, {
+      name: lab._current.experiments[0].tests[0].title,
+      passed
+    }, done);
+  });
+
+  lab.test('loads', () => {
+    driver.get(Helper.JSPERF_HOST);
+
+    return driver.getTitle().then(function (title) {
+      Code.expect(title).to.include('jsPerf');
+      passed = true;
+    });
+  });
+
+  lab.test('create page', () => {
+    function login () {
+      function clickLoginBtn () {
+        return driver.findElement({ css: 'a.login' }).then((el) => el.click());
+      }
+
+      return clickLoginBtn()
+      .then(() => {
+        // GitHub Login
+        // Note: should use an account _without_ 2FA
+        return driver.findElement({ id: 'login_field' })
+        .then((el) => el.sendKeys(process.env.E2E_GITHUB_USER))
+        .then(() => driver.findElement({ id: 'password' }))
+        .then((el) => el.sendKeys(process.env.E2E_GITHUB_PASS))
+        .then(() => driver.findElement({ tagName: 'form' }))
+        .then((el) => el.submit());
+      })
+      .then(() => {
+        // Need to (re-)authorize?
+        return driver.getTitle().then((title) => {
+          if (title.indexOf('Authorize') > -1) {
+            // authorize OAuth app in GitHub
+            return driver.findElement({ css: 'button.btn.btn-primary' })
+            .then((el) => el.click());
+          }
+        });
+      })
+      .then(() => driver.findElements({ tagName: 'form' })
+        .then((els) => {
+          // already logged in
+          if (els.length === 0) {
+            // sometimes returning from GitHub requires another button click
+            return clickLoginBtn()
+            .then(() => driver.findElements({ tagName: 'form' })
+              .then((els) => {
+                Code.expect(els).to.have.length(1);
+                return els[0];
+              })
+            );
+          }
+
+          Code.expect(els).to.have.length(1);
+          return els[0];
+        }));
+    }
+
+    return driver.get(Helper.JSPERF_HOST)
+    .then(() => driver.findElements({ tagName: 'form' }))
+    .then((els) => {
+      // if form is displayed on home page, we are logged in
+      if (els.length === 0) {
+        return login();
+      }
+
+      Code.expect(els).to.have.length(1);
+      return els[0];
+    })
+    .then((formEl) => {
+      // fill out form
+      function fillOut (id, value) {
+        return driver.findElement({ id }).then((el) => el.sendKeys(value));
+      }
+
+      function check (id) {
+        return driver.findElement({ id }).then((el) => el.click());
+      }
+
+      const now = Date.now();
+
+      return Promise.all([
+        fillOut('author', 'Max Tester'),
+        fillOut('authorEmail', 'test@jsperf.test'),
+        fillOut('authorURL', 'http://jsperf.test'),
+        fillOut('title', 'Test ' + now),
+        check('visible'),
+        fillOut('info', 'This is my test at ' + now),
+        fillOut('initHTML', `<p>${now}</p>`),
+        fillOut('setup', 'var a = [];'),
+        fillOut('teardown', 'delete a;'),
+        fillOut('test[0][title]', `Title ${now} 0`),
+        // TODO defer checkbox
+        fillOut('test[0][code]', `a.push(${now});`),
+        fillOut('test[1][title]', `Title ${now} 1`),
+        // TODO defer checkbox
+        fillOut('test[1][code]', `a[0] = ${now};`)
+      ])
+      .then(() => formEl.submit());
+    })
+    .then(() => driver.getCurrentUrl())
+    .then((newUrl) => {
+      // should redirect to new /slug page
+      Code.expect(newUrl.length).to.be.greaterThan(Helper.JSPERF_HOST.length);
+      passed = true;
+    });
+  });
+
+  lab.test('browse', () => {
+    let slug;
+    return driver.get(Helper.JSPERF_HOST)
+    .then(() => driver.findElement({ linkText: 'Latest' }).then((el) => el.click()))
+    .then(() => driver.getTitle().then((title) => {
+      Code.expect(title).to.include('Browse');
+
+      return driver.findElement({ css: 'article ul li a' })
+        .then((el) => {
+          el.getAttribute('href').then((href) => {
+            slug = href;
+          });
+          return el.click();
+        });
+    }))
+    .then(() => driver.getCurrentUrl())
+    .then((newUrl) => {
+      Code.expect(newUrl).to.include(slug);
+      passed = true;
+    });
+  });
+});
diff --git a/test/unit/server/repositories/pages.js b/test/unit/server/repositories/pages.js
index 3d3ca4b..476b415 100644
--- a/test/unit/server/repositories/pages.js
+++ b/test/unit/server/repositories/pages.js
@@ -256,7 +256,7 @@ lab.experiment('Pages Repository', function () {
       .then(function (page) {
         Code.expect(page).to.be.array();
         Code.expect(genericQueryStub.calledWithExactly(
-          'SELECT * FROM (SELECT x.id AS pID, x.slug AS url, x.revision, x.title, x.published, x.updated, COUNT(x.slug) AS revisionCount FROM pages x WHERE x.title LIKE ? OR x.info LIKE ? GROUP BY x.slug ORDER BY updated DESC LIMIT 0, 50) y LEFT JOIN (SELECT t.pageid, COUNT(t.pageid) AS testCount FROM tests t GROUP BY t.pageid) z ON z.pageid = y.pID;',
+          'SELECT * FROM (SELECT x.id AS pID, x.slug AS url, x.revision, x.title, x.published, x.updated, COUNT(x.slug) AS revisionCount FROM pages x WHERE x.title LIKE ? OR x.info LIKE ? GROUP BY x.slug ORDER BY updated DESC LIMIT 0, 50) y LEFT JOIN (SELECT t.pageID, COUNT(t.pageID) AS testCount FROM tests t GROUP BY t.pageID) z ON z.pageID = y.pID;',
           [wc, wc]
         )).to.be.true();