Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix flaky browser tests #484

Merged
merged 6 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ jobs:
fail-fast: false
matrix:
node-version:
- 18
- 16
- 14
steps:
- uses: actions/checkout@v3
Expand Down
88 changes: 26 additions & 62 deletions test/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import busboy from 'busboy';
import express from 'express';
import {Page} from 'playwright-chromium';
import ky from '../source/index.js';
import {createHttpTestServer, HttpServerOptions} from './helpers/create-http-test-server.js';
import {createHttpTestServer, ExtendedHttpTestServer, HttpServerOptions} from './helpers/create-http-test-server.js';
import {parseRawBody} from './helpers/parse-body.js';
import {withPage} from './helpers/with-page.js';

Expand All @@ -17,6 +17,10 @@ const DIST_DIR = new URL('../distribution', import.meta.url).toString();
const createEsmTestServer = async (options?: HttpServerOptions) => {
const server = await createHttpTestServer(options);
server.use('/distribution', express.static(DIST_DIR.replace(/^file:\/\//, '')));
server.use((_, response, next) => {
response.set('Connection', 'close');
next();
});
return server;
};

Expand All @@ -32,9 +36,16 @@ const addKyScriptToPage = async (page: Page) => {
await page.waitForFunction(() => typeof window.ky === 'function');
};

test('prefixUrl option', withPage, async (t: ExecutionContext, page: Page) => {
const server = await createEsmTestServer();
let server: ExtendedHttpTestServer;
test.beforeEach(async () => {
server = await createEsmTestServer();
});

test.afterEach(async () => {
await server.close();
});

test.serial('prefixUrl option', withPage, async (t: ExecutionContext, page: Page) => {
server.get('/', (_request, response) => {
response.end('zebra');
});
Expand All @@ -60,13 +71,9 @@ test('prefixUrl option', withPage, async (t: ExecutionContext, page: Page) => {
]), server.url);

t.deepEqual(results, ['rainbow', 'rainbow', 'rainbow', 'rainbow']);

await server.close();
});

test('aborting a request', withPage, async (t: ExecutionContext, page: Page) => {
const server = await createEsmTestServer();

test.serial('aborting a request', withPage, async (t: ExecutionContext, page: Page) => {
server.get('/', (_request, response) => {
response.end('meow');
});
Expand All @@ -89,15 +96,12 @@ test('aborting a request', withPage, async (t: ExecutionContext, page: Page) =>

// TODO: When targeting Node.js 18, also assert that the error is a DOMException
t.is(error.split(': ')[1], '🦄');

await server.close();
});

test('should copy origin response info when using `onDownloadProgress`', withPage, async (t: ExecutionContext, page: Page) => {
test.serial('should copy origin response info when using `onDownloadProgress`', withPage, async (t: ExecutionContext, page: Page) => {
const json = {hello: 'world'};
const status = 202;
const statusText = 'Accepted';
const server = await createEsmTestServer();
server.get('/', (_request, response) => {
response.end('meow');
});
Expand Down Expand Up @@ -127,13 +131,11 @@ test('should copy origin response info when using `onDownloadProgress`', withPag
statusText,
data: json,
});
await server.close();
});

test('should not copy response body with 204 status code when using `onDownloadProgress` ', withPage, async (t: ExecutionContext, page: Page) => {
test.serial('should not copy response body with 204 status code when using `onDownloadProgress` ', withPage, async (t: ExecutionContext, page: Page) => {
const status = 204;
const statusText = 'No content';
const server = await createEsmTestServer();
server.get('/', (_request, response) => {
response.end('meow');
});
Expand Down Expand Up @@ -178,13 +180,9 @@ test('should not copy response body with 204 status code when using `onDownloadP
totalBytes: data.totalBytes,
transferredBytes: 0,
}]);

await server.close();
});

test('aborting a request with onDonwloadProgress', withPage, async (t: ExecutionContext, page: Page) => {
const server = await createEsmTestServer();

test.serial('aborting a request with onDonwloadProgress', withPage, async (t: ExecutionContext, page: Page) => {
server.get('/', (_request, response) => {
response.end('meow');
});
Expand Down Expand Up @@ -214,16 +212,12 @@ test('aborting a request with onDonwloadProgress', withPage, async (t: Execution
}, server.url);
// This should be an AbortError like in the 'aborting a request' test, but there is a bug in Chromium
t.is(error, 'TypeError: Failed to fetch');

await server.close();
});

test(
test.serial(
'throws TimeoutError even though it does not support AbortController',
withPage,
async (t: ExecutionContext, page: Page) => {
const server = await createEsmTestServer();

server.get('/', (_request, response) => {
response.end();
});
Expand Down Expand Up @@ -252,14 +246,10 @@ test(

t.is(error.message, 'TimeoutError: Request timed out');
t.is(error.request.url, `${server.url}/slow`);

await server.close();
},
);

test('onDownloadProgress works', withPage, async (t: ExecutionContext, page: Page) => {
const server = await createEsmTestServer();

test.serial('onDownloadProgress works', withPage, async (t: ExecutionContext, page: Page) => {
server.get('/', (_request, response) => {
response.writeHead(200, {
'content-length': '4',
Expand Down Expand Up @@ -297,13 +287,9 @@ test('onDownloadProgress works', withPage, async (t: ExecutionContext, page: Pag
[{percent: 1, transferredBytes: 4, totalBytes: 4}, 'ow'],
]);
t.is(result.text, 'meow');

await server.close();
});

test('throws if onDownloadProgress is not a function', withPage, async (t: ExecutionContext, page: Page) => {
const server = await createEsmTestServer();

test.serial('throws if onDownloadProgress is not a function', withPage, async (t: ExecutionContext, page: Page) => {
server.get('/', (_request, response) => {
response.end();
});
Expand All @@ -317,13 +303,9 @@ test('throws if onDownloadProgress is not a function', withPage, async (t: Execu
return request.catch(error_ => error_.toString());
}, server.url);
t.is(error, 'TypeError: The `onDownloadProgress` option must be a function');

await server.close();
});

test('throws if does not support ReadableStream', withPage, async (t: ExecutionContext, page: Page) => {
const server = await createEsmTestServer();

test.serial('throws if does not support ReadableStream', withPage, async (t: ExecutionContext, page: Page) => {
server.get('/', (_request, response) => {
response.end();
});
Expand All @@ -338,15 +320,11 @@ test('throws if does not support ReadableStream', withPage, async (t: ExecutionC
return request.catch(error_ => error_.toString());
}, server.url);
t.is(error, 'Error: Streams are not supported in your environment. `ReadableStream` is missing.');

await server.close();
});

test('FormData with searchParams', withPage, async (t: ExecutionContext, page: Page) => {
test.serial('FormData with searchParams', withPage, async (t: ExecutionContext, page: Page) => {
t.plan(3);

const server = await createEsmTestServer({bodyParser: false});

server.get('/', (_request, response) => {
response.end();
});
Expand Down Expand Up @@ -374,15 +352,11 @@ test('FormData with searchParams', withPage, async (t: ExecutionContext, page: P
body: formData,
});
}, server.url);

await server.close();
});

test('FormData with searchParams ("multipart/form-data" parser)', withPage, async (t: ExecutionContext, page: Page) => {
test.serial('FormData with searchParams ("multipart/form-data" parser)', withPage, async (t: ExecutionContext, page: Page) => {
t.plan(3);

const server = await createEsmTestServer();

server.get('/', (_request, response) => {
response.end();
});
Expand Down Expand Up @@ -447,18 +421,14 @@ test('FormData with searchParams ("multipart/form-data" parser)', withPage, asyn
body: formData,
});
}, server.url);

await server.close();
});

test(
test.serial(
'headers are preserved when input is a Request and there are searchParams in the options',
withPage,
async (t: ExecutionContext, page: Page) => {
t.plan(2);

const server = await createEsmTestServer();

server.get('/', (_request, response) => {
response.end();
});
Expand All @@ -483,18 +453,14 @@ test(
})
.text();
}, server.url);

await server.close();
},
);

test('retry with body', withPage, async (t: ExecutionContext, page: Page) => {
test.serial('retry with body', withPage, async (t: ExecutionContext, page: Page) => {
t.plan(4);

let requestCount = 0;

const server = await createEsmTestServer();

server.get('/', (_request, response) => {
response.end('zebra');
});
Expand All @@ -518,6 +484,4 @@ test('retry with body', withPage, async (t: ExecutionContext, page: Page) => {
);

t.is(requestCount, 2);

await server.close();
});
4 changes: 2 additions & 2 deletions test/helpers/with-page.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import process from 'node:process';
import type {ExecutionContext, UntitledMacro} from 'ava';
import type {ExecutionContext, Implementation} from 'ava';
import {chromium, Page} from 'playwright-chromium';

type Run = (t: ExecutionContext, page: Page) => Promise<void>;

const PWDEBUG = Boolean(process.env['PWDEBUG']);

export const withPage: UntitledMacro<any[]> = async (t: ExecutionContext, run: Run): Promise<void> => {
export const withPage: Implementation<any[]> = async (t: ExecutionContext, run: Run): Promise<void> => {
const browser = await chromium.launch({
devtools: PWDEBUG,
});
Expand Down