Skip to content

Commit

Permalink
Allow End-to-End Testing as an Admin or Tester (#908)
Browse files Browse the repository at this point in the history
* Initial smoke test before testing in CI

* Make adjustments for CI

* Testing if it is just slow

* Comment out new test

* Reenable test

* Try headless chrome

* Try running just one test

* Add logging

* Add more logging

* Found potential environmental issue

* Try fixing database connection

* Try turning off sandbox mode

* Try using a package to kill the server

* Run full test suite instead of just one test

* Some final polish

* PR polish

* Enable sign-in in e2e tests

* Work on fixing tests

* Work on fixing tests

* Work on fixing tests

* Work on fixing tests

* Work on fixing tests

* Work on fixing tests

* Work on fixing tests

* Work on fixing tests

* Work on fixing tests

* Work on fixing tests

* Work on fixing tests

* Carry over changes from main

* Add todo to list of todos

* PR feedback

* Merge config

* Use services.deprecated to fix test

* Fix sandbox config
  • Loading branch information
alflennik authored Mar 5, 2024
1 parent c3bf11e commit 6263acf
Show file tree
Hide file tree
Showing 19 changed files with 382 additions and 336 deletions.
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,20 @@
"windows": {
"program": "${workspaceFolder}/server/node_modules/jest/bin/jest"
}
},
{
"type": "node",
"request": "launch",
"name": "Jest Client Debug Current Test File",
//"env": { "NODE_ENV": "test" },
"envFile": "${workspaceFolder}/config/test.env",
"program": "${workspaceFolder}/client/node_modules/.bin/jest",
"args": ["${fileBasenameNoExtension}", "--config", "client/jest.setup.js"],
"console": "integratedTerminal",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/client/node_modules/jest/bin/jest"
}
}
]
}
4 changes: 1 addition & 3 deletions client/components/App/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ import { evaluateAuth } from '../../utils/evaluateAuth';
import ScrollFixer from '../../utils/ScrollFixer';
import routes from '../../routes';
import { ME_QUERY } from './queries';
import useSigninUrl from './useSigninUrl';
import SkipLink from '../SkipLink';
import './App.css';

const App = () => {
const location = useLocation();
const signinUrl = useSigninUrl();
const { client, loading, data } = useQuery(ME_QUERY);
const [isNavbarExpanded, setIsNavbarExpanded] = useState(false);

Expand Down Expand Up @@ -159,7 +157,7 @@ const App = () => {
as={Link}
to="#"
onClick={() =>
(window.location.href = signinUrl)
(window.location.href = `/api/auth/oauth`)
}
>
Sign in with GitHub
Expand Down
12 changes: 0 additions & 12 deletions client/components/App/useSigninUrl.js

This file was deleted.

30 changes: 30 additions & 0 deletions client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,33 @@ root.render(
</AriaLiveRegionProvider>
</GraphQLProvider>
);

const signMeInCommon = async user => {
if (!user.username) throw new Error('Please provide a username');

const response = await fetch('/api/auth/fake-user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(user)
});

const responseText = await response.text();
if (!response.ok) throw responseText;

location.reload();
};

window.signMeInAsAdmin = username => {
return signMeInCommon({
username,
roles: [{ name: 'ADMIN' }, { name: 'TESTER' }, { name: 'VENDOR' }]
});
};

window.signMeInAsTester = username => {
return signMeInCommon({ username, roles: [{ name: 'TESTER' }] });
};

window.signMeInAsVendor = username => {
return signMeInCommon({ username, roles: [{ name: 'VENDOR' }] });
};
166 changes: 56 additions & 110 deletions client/tests/smokeTest.test.js
Original file line number Diff line number Diff line change
@@ -1,142 +1,88 @@
const puppeteer = require('puppeteer');
const path = require('path');
const spawn = require('cross-spawn');
const treeKill = require('tree-kill');
import getPage, { setup, teardown } from './util/getPage';

/*
End-to-End Testing TODO:
- The server start and close functions should be handled in one place by
Jest's setup and teardown scripts.
- There needs to be a utility function to enable common tasks like getting a
fresh end-to-end page.
- End-to-end testing should support signing in with different roles, A.K.A. as
an admin, tester or vendor.
- It should be possible to reset the database to a predictable state after each
test, ideally in such a way that concurrency remains possible. See the POC
here: https://github.com/w3c/aria-at-app/pull/895
- Add section to docs for end-to-end testing
*/

const PORT = 8033;
const CLIENT_PORT = 3033;
const AUTOMATION_SCHEDULER_PORT = 8833;

const startServer = async serverOrClient => {
return new Promise(resolve => {
const server = spawn('yarn', ['workspace', serverOrClient, 'dev'], {
cwd: path.resolve(__dirname, '../../'),
env: {
PATH: process.env.PATH,
PORT,
CLIENT_PORT: 3033,
AUTOMATION_SCHEDULER_PORT: 8833,
API_SERVER: `http://localhost:${PORT}`,
APP_SERVER: `http://localhost:${CLIENT_PORT}`,
AUTOMATION_SCHEDULER_URL: `http://localhost:${AUTOMATION_SCHEDULER_PORT}`,
PGDATABASE: 'aria_at_report_test',
PGPORT: 5432,
ENVIRONMENT: 'test'
}
});

const killServer = async () => {
await new Promise((resolve, reject) => {
treeKill(server.pid, error => {
if (error) return reject(error);
resolve();
});
});
};
describe('smoke test', () => {
beforeAll(async () => {
await setup();
}, 30000);

server.stdout.on('data', data => {
const output = data.toString();
console.info(output); // eslint-disable-line no-console
afterAll(async () => {
await teardown();
}, 30000);

if (
(serverOrClient === 'server' &&
output.includes(`Listening on ${PORT}`)) ||
(serverOrClient === 'client' &&
output.includes('compiled successfully'))
) {
resolve({ close: killServer });
}
});
it('can sign in as admin, tester, vendor, or logged out', async () => {
await Promise.all([
getPage({ role: 'admin', url: '/test-queue' }, async page => {
// Only admins can remove rows from the test queue
await page.waitForSelector('td.actions ::-p-text(Remove)');
}),

server.stderr.on('data', data => {
const output = data.toString();
console.info(output); // eslint-disable-line no-console
});
});
};
getPage({ role: 'tester', url: '/test-queue' }, async page => {
// Testers can assign themselves
await page.waitForSelector('table ::-p-text(Assign Yourself)');
const adminOnlyRemoveButton = await page.$(
'td.actions ::-p-text(Remove)'
);
expect(adminOnlyRemoveButton).toBe(null);
}),

describe('smoke test', () => {
let browser;
let backendServer;
let clientServer;
getPage(
{ role: 'vendor', url: '/test-queue' },
async (page, { baseUrl }) => {
// Vendors get the same test queue as signed-out users
await page.waitForSelector(
'td.actions ::-p-text(View tests)'
);
// Unlike signed-out users, they will get tables on this page
await page.goto(`${baseUrl}/candidate-review`);
await page.waitForSelector('table');
}
),

beforeAll(async () => {
// eslint-disable-next-line no-console
console.info(
'Starting dev servers. This is required for end-to-end testing'
);
[clientServer, backendServer] = await Promise.all([
startServer('client'),
startServer('server')
getPage({ role: false, url: '/test-queue' }, async page => {
// Signed-out users can only view tests, not run them
await page.waitForSelector('td.actions ::-p-text(View tests)');
})
]);

browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox'] // Required for GitHub environment
});
const [extraBlankPage] = await browser.pages();
extraBlankPage.close();
}, 60000);

afterAll(async () => {
await Promise.all([backendServer.close(), clientServer.close()]);

// Browser might not be defined, if it failed to start
if (browser) await browser.close();
}, 60000);
}, 10000);

it('loads various pages without crashing', async () => {
let homeH1;
let reportsH1;
let dataManagementH1;

await Promise.all([
(async () => {
const page = await browser.newPage();
await page.goto(`http://localhost:${CLIENT_PORT}/`);
getPage({ role: false, url: '/' }, async page => {
await page.waitForSelector('h1');
const h1Handle = await page.waitForSelector('h1');
homeH1 = await h1Handle.evaluate(h1 => h1.innerText);
})(),
(async () => {
const page = await browser.newPage();
await page.goto(`http://localhost:${CLIENT_PORT}/reports`);
const h1Text = await h1Handle.evaluate(h1 => h1.innerText);
expect(h1Text).toBe(
'Enabling Interoperability for Assistive Technology Users'
);
}),
getPage({ role: false, url: '/reports' }, async page => {
// Wait for an h2 because an h1 will show while the page is
// still loading
await page.waitForSelector('h2');
const h1Handle = await page.waitForSelector('h1');
reportsH1 = await h1Handle.evaluate(h1 => h1.innerText);
})(),
(async () => {
const page = await browser.newPage();
await page.goto(
`http://localhost:${CLIENT_PORT}/data-management`
const h1Text = await h1Handle.evaluate(h1 => h1.innerText);
expect(h1Text).toBe(
'Assistive Technology Interoperability Reports'
);
}),
getPage({ role: false, url: '/data-management' }, async page => {
// Wait for an h2 because an h1 will show while the page is
// still loading
await page.waitForSelector('h2');
const h1Handle = await page.waitForSelector('h1');
dataManagementH1 = await h1Handle.evaluate(h1 => h1.innerText);
})()
const h1Text = await h1Handle.evaluate(h1 => h1.innerText);
expect(h1Text).toBe('Data Management');
})
]);

expect(homeH1).toBe(
'Enabling Interoperability for Assistive Technology Users'
);
expect(reportsH1).toBe('Assistive Technology Interoperability Reports');
expect(dataManagementH1).toBe('Data Management');
}, 60000);
}, 10000);
});
Loading

0 comments on commit 6263acf

Please sign in to comment.