Skip to content

Commit

Permalink
Tests: more general file upload & enable Safari
Browse files Browse the repository at this point in the history
  • Loading branch information
corrideat committed Jun 23, 2024
1 parent fc4aebe commit 6c478f3
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 41 deletions.
9 changes: 3 additions & 6 deletions .github/workflows/integration-tests-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,9 @@ jobs:
- chrome
- firefox
- MicrosoftEdge
# include:
# # TODO: Currently, testing on Safari doesn't work. It seems
# # like the the 'about:blank' form submission could be the
# # issue.
# - os: macos-latest
# browser: safari
include:
- os: macos-latest
browser: safari
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
Expand Down
80 changes: 68 additions & 12 deletions src/utils/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const boundaryMatchRegex =
/;\s*boundary=(?:"([0-9a-zA-Z'()+_,\-./:=? ]{0,69}[0-9a-zA-Z'()+_,\-./:=?])"|([0-9a-zA-Z'+_\-.]{0,69}[0-9a-zA-Z'+_\-.]))/;

const server = http.createServer((req, res) => {
const okHandler = (data: Buffer) => {
const firstScriptIndex = Buffer.from(data).indexOf('<script');
Expand All @@ -31,7 +34,7 @@ const server = http.createServer((req, res) => {
['content-type', 'text/html; charset=UTF-8'],
[
'content-security-policy',
"default-src 'none'; script-src 'self' 'unsafe-eval' data:; script-src-elem blob: data:; script-src-attr 'none'; style-src data:; child-src blob:; connect-src blob: data:; frame-ancestors 'self'; form-action data:",
"default-src 'none'; script-src 'self' 'unsafe-eval' data:; script-src-elem blob: data:; script-src-attr 'none'; style-src data:; child-src blob:; connect-src blob: data:; frame-ancestors 'self'; form-action 'self' data:",
],
[
'permissions-policy',
Expand Down Expand Up @@ -92,7 +95,37 @@ const server = http.createServer((req, res) => {
),
);
} catch (e) {
console.error('Error sending index', e);
console.error('Error sending /blank', e);
res.writeHead(
500,
(e instanceof Error && e?.message) ||
String(e) ||
'Unknown error',
);
} finally {
res.end();
}
} else if (req.method === 'GET' && req.url === '/echo-document') {
try {
okHandler(
Buffer.from(
'<!DOCTYPE html>' +
'<html xml:lang="zxx" xmlns="http://www.w3.org/1999/xhtml" lang="zxx">' +
'<head>' +
'<meta charset="UTF-8"/>' +
'<title>Echo document</title>' +
'</head>' +
'<body>' +
'<form enctype="multipart/form-data" method="POST">' +
'<input type="file" name="__TEXT__"/>' +
'<button type="submit">Submit</button>' +
'</form>' +
'</body>' +
'</html>',
),
);
} catch (e) {
console.error('Error sending /echo-document', e);
res.writeHead(
500,
(e instanceof Error && e?.message) ||
Expand All @@ -102,7 +135,23 @@ const server = http.createServer((req, res) => {
} finally {
res.end();
}
} else if (req.method === 'POST' && req.url === '/') {
} else if (req.method === 'POST' && req.url === '/echo-document') {
if (
!req.headers['content-type'] ||
!req.headers['content-type'].startsWith('multipart/form-data;')
) {
res.writeHead(415).end();
return;
}

const boundaryMatch =
req.headers['content-type'].match(boundaryMatchRegex);
if (!boundaryMatch || (!boundaryMatch[1] && !boundaryMatch[2])) {
res.writeHead(422).end();
return;
}
const boundary = boundaryMatch[1] || boundaryMatch[2];

new Promise<Buffer>((resolve) => {
const chunks: Buffer[] = [];
req.on('data', (chunk: Buffer) => {
Expand All @@ -111,18 +160,25 @@ const server = http.createServer((req, res) => {
req.on('end', () => {
const result = Buffer.concat(chunks);

// Special handling to allow 'text/plain' form submissions
if (req.headers['content-type'] === 'text/plain') {
const idx = result.indexOf('__TEXT__=');
if (idx >= 0) {
resolve(result.subarray(idx + '__TEXT__='.length));
return;
}
}

resolve(result);
});
})
.then((buffer) => {
const startMultipartBody = buffer.indexOf('--' + boundary);
const startMultipartData = buffer.indexOf(
'\r\n\r\n',
startMultipartBody + 2 + boundary.length,
);
const endMultipartData = buffer.indexOf(
'\r\n--' + boundary + '--',
startMultipartData,
);

return buffer.subarray(
startMultipartData + 4,
endMultipartData,
);
})
.then(okHandler)
.catch((e) => {
console.error('Error sending index', e);
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/basic-functionality.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ test('Basic functionality', async (t) => {
});

await t.test('Can decrypt a file', { ['skip']: !file }, async () => {
navigateToFile(driver, baseUrl, file as File);
await navigateToFile(driver, baseUrl, file as File);

await driver.wait(until.elementLocated(By.css('form')));

Expand Down
10 changes: 5 additions & 5 deletions test/e2e/lib/dragAndDropFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,30 @@ import type { WebElement } from 'selenium-webdriver';
// Based on:
// * <https://stackoverflow.com/questions/38829153/selenium-drag-and-drop-from-file-system-to-webdriver>,
// * <https://gist.github.com/florentbr/349b1ab024ca9f3de56e6bf8af2ac69e>
const JS_DROP_FILE = `const element = arguments[0],
const JS_DROP_FILE = `const target$ = arguments[0],
contents = new Uint8Array(arguments[1]),
filename = arguments[2],
type = arguments[3],
offsetX = arguments[4],
offsetY = arguments[5];
const doc = element.ownerDocument || document;
const doc = target$.ownerDocument || document;
let target, clientX, clientY;
for (let i = 0; ; ) {
const box = element.getBoundingClientRect(),
const box = target$.getBoundingClientRect(),
clientX = box.left + (offsetX || box.width / 2),
clientY = box.top + (offsetY || box.height / 2);
target = doc.elementFromPoint(clientX, clientY);
if (target && element.contains(target)) break;
if (target && target$.contains(target)) break;
if (++i > 1) {
throw new Error('Element not interactable');
}
element.scrollIntoView({
target$.scrollIntoView({
behavior: 'instant',
block: 'center',
inline: 'center',
Expand Down
36 changes: 19 additions & 17 deletions test/e2e/lib/navigateToFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,36 @@
*/

import type { WebDriver, WebElement } from 'selenium-webdriver';
import { until } from 'selenium-webdriver';
import { By, until } from 'selenium-webdriver';
import waitUntilReadyStateComplete from './waitUntilReadyStateComplete.js';

const navigateToFile_ = async (driver: WebDriver, url: URL, file: File) => {
driver.get('about:blank');
driver.get(new URL('echo-document', url).toString());
await driver.executeScript(
'document.documentElement.style.setProperty("display", "none", "important");',
);
await waitUntilReadyStateComplete(driver);
const document$: WebElement = await driver.executeScript(
'return document.documentElement;',
);
const fileInput$ = await driver.findElement(By.css('input[type="file"]'));
await driver.executeScript(
`
const ns = 'http://www.w3.org/1999/xhtml';
const form$ = document.createElementNS(ns, 'form');
form$.setAttribute('action', arguments[0]);
form$.setAttribute('enctype', 'text/plain');
form$.setAttribute('method', 'POST');
const textarea$ = document.createElementNS(ns, 'textarea');
textarea$.setAttribute('name', '__TEXT__');
textarea$.value = arguments[1];
form$.appendChild(textarea$);
form$.style.setProperty('transform', 'scale(0)', 'important');
document.body.appendChild(form$);
form$.submit();
`,
url.toString(),
Buffer.from(await file.arrayBuffer()).toString('utf-8'),
const target$ = arguments[0],
contents = new Uint8Array(arguments[1]),
filename = arguments[2],
type = arguments[3];
const dataTransfer = new DataTransfer();
dataTransfer.effectAllowed = 'all';
dataTransfer.items.add(new File([contents], filename, { type }));
target$.files = dataTransfer.files;
`,
fileInput$,
Array.from(new Uint8Array(await file.arrayBuffer())),
file.name,
file.type,
);
await driver.findElement(By.css('form')).submit();
// Wait until navigation happens
await driver.wait(until.stalenessOf(document$));
};
Expand Down

0 comments on commit 6c478f3

Please sign in to comment.