Skip to content

Commit fed66fe

Browse files
authored
Merge pull request #10 from cloudnc/feat/mock-route-at-context-level
feat(playwright): mock route at context level
2 parents 7efc91d + 9606674 commit fed66fe

File tree

11 files changed

+81
-91
lines changed

11 files changed

+81
-91
lines changed

.github/workflows/main.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ jobs:
99
build:
1010
# Machine environment:
1111
# https://help.github.com/en/articles/software-in-virtual-environments-for-github-actions#ubuntu-1804-lts
12-
# We specify the Node.js version manually below, and use versioned Chrome from Puppeteer.
13-
runs-on: ubuntu-18.04
12+
# We specify the Node.js version manually below
13+
runs-on: ubuntu-22.04
1414

1515
steps:
16-
- uses: actions/checkout@v1
17-
- name: Use Node.js 16.14.2
18-
uses: actions/setup-node@v1
16+
- uses: actions/checkout@v3
17+
- name: Use Node.js 18.4.0
18+
uses: actions/setup-node@v3
1919
with:
20-
node-version: 16.14.2
20+
node-version: 18.4.0
2121
- name: Install dependencies
2222
run: yarn --frozen-lockfile --non-interactive --no-progress
2323
- name: Format check

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v16.14.2
1+
v18.4.0

.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"printWidth": 120,
3+
"singleQuote": true,
4+
"trailingComma": "all",
5+
"htmlWhitespaceSensitivity": "ignore",
6+
"tabWidth": 2
7+
}

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
"dependencies": {
1515
"@grpc/grpc-js": "1.6.10",
1616
"@improbable-eng/grpc-web": "0.15.0",
17-
"@playwright/test": "1.22.2",
17+
"@playwright/test": "1.35.1",
1818
"google-protobuf": "3.14.0",
19-
"playwright-core": "1.22.2"
19+
"playwright-core": "1.35.1"
2020
},
2121
"devDependencies": {
22-
"prettier": "^2.7.1",
22+
"prettier": "2.8.8",
2323
"semantic-release": "19.0.2",
2424
"typescript": "4.7.2"
2525
}

src/base/index.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Metadata, status as Status } from "@grpc/grpc-js";
1+
import { Metadata, status as Status } from '@grpc/grpc-js';
22

33
export interface GrpcErrorResponse {
44
status: Status;
@@ -22,7 +22,7 @@ function fourBytesLength(sized: { length: number }): Uint8Array {
2222

2323
export function decodeGrpcWebBody(bodyBuffer: Buffer): GrpcResponse {
2424
if (bodyBuffer.length === 0) {
25-
throw new Error("Body has zero length, cannot decode!");
25+
throw new Error('Body has zero length, cannot decode!');
2626
}
2727

2828
const bodyRaw = new Uint8Array(bodyBuffer);
@@ -53,7 +53,7 @@ export function decodeGrpcWebBody(bodyBuffer: Buffer): GrpcResponse {
5353
const trailersHeader = 0x80;
5454

5555
if (bodyRaw.at(offset++) !== trailersHeader) {
56-
throw new Error("Expected trailers header 0x80");
56+
throw new Error('Expected trailers header 0x80');
5757
}
5858

5959
const trailersLength = readInt32Length(bodyRaw, offset);
@@ -66,16 +66,16 @@ export function decodeGrpcWebBody(bodyBuffer: Buffer): GrpcResponse {
6666

6767
const trailers = new Metadata();
6868

69-
trailersString.split("\r\n").forEach((trailer) => {
70-
const [key, value] = trailer.split(":", 2);
69+
trailersString.split('\r\n').forEach((trailer) => {
70+
const [key, value] = trailer.split(':', 2);
7171
trailers.set(key, value);
7272
});
7373

7474
if (status !== Status.OK) {
7575
return {
7676
status,
7777
trailers,
78-
detail: trailers.get("grpc-message")[0] as string | undefined,
78+
detail: trailers.get('grpc-message')[0] as string | undefined,
7979
};
8080
}
8181

@@ -99,19 +99,17 @@ export class GrpcUnknownStatus extends Error {
9999

100100
export function grpcResponseToBuffer(response: GrpcResponse): Buffer {
101101
// error messages need to have a zero length message field to be considered valid
102-
const message = "message" in response ? response.message : new Uint8Array();
102+
const message = 'message' in response ? response.message : new Uint8Array();
103103

104104
// all success responses have status OK
105-
const status = "status" in response ? response.status : Status.OK;
105+
const status = 'status' in response ? response.status : Status.OK;
106106
// error statuses may the detail field to denote a custom error message, otherwise use the string version of the status
107107
let grpcMessage: string | undefined;
108108

109-
if ("detail" in response) {
109+
if ('detail' in response) {
110110
grpcMessage = response.detail;
111111
} else {
112-
const currentStatus = Object.entries(Status).find(
113-
([, code]) => code === status
114-
);
112+
const currentStatus = Object.entries(Status).find(([, code]) => code === status);
115113

116114
if (!currentStatus) {
117115
throw new GrpcUnknownStatus(status);

src/playwright/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { observeGrpcUnary } from "./observe-grpc-unary";
2-
export { mockGrpcUnary } from "./mock-grpc-unary";
3-
export { readGrpcRequest } from "./read-grpc-request";
4-
export * from "./interfaces";
1+
export { observeGrpcUnary } from './observe-grpc-unary';
2+
export { mockGrpcUnary } from './mock-grpc-unary';
3+
export { readGrpcRequest } from './read-grpc-request';
4+
export * from './interfaces';

src/playwright/interfaces.ts

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
import { grpc } from "@improbable-eng/grpc-web";
2-
import { Request } from "playwright-core";
3-
import { status as Status, Metadata } from "@grpc/grpc-js";
1+
import { grpc } from '@improbable-eng/grpc-web';
2+
import { Request } from 'playwright-core';
3+
import { status as Status, Metadata } from '@grpc/grpc-js';
44

5-
export interface UnaryMethodDefinitionish
6-
extends grpc.UnaryMethodDefinition<any, any> {
5+
export interface UnaryMethodDefinitionish extends grpc.UnaryMethodDefinition<any, any> {
76
requestStream: any;
87
responseStream: any;
98
}
109

11-
export type RequestPredicate = (
12-
requestMessage: Uint8Array | null,
13-
request: Request
14-
) => boolean | Promise<boolean>;
10+
export type RequestPredicate = (requestMessage: Uint8Array | null, request: Request) => boolean | Promise<boolean>;
1511

1612
export interface MockedGrpcCall {
1713
/**
@@ -21,9 +17,7 @@ export interface MockedGrpcCall {
2117
* The request message argument to the optional predicate should be used to match the request payload.
2218
* Note the requestMessage objects need to be decoded using a protobuf decoder for the specific expected message.
2319
*/
24-
waitForMock(
25-
requestPredicate?: RequestPredicate
26-
): Promise<{ requestMessage: Uint8Array | null }>;
20+
waitForMock(requestPredicate?: RequestPredicate): Promise<{ requestMessage: Uint8Array | null }>;
2721
}
2822

2923
export interface ObservedGrpcCallResponse {
@@ -34,7 +28,5 @@ export interface ObservedGrpcCallResponse {
3428
}
3529

3630
export interface ObservedGrpcCall {
37-
waitForResponse: (
38-
requestPredicate?: RequestPredicate
39-
) => Promise<ObservedGrpcCallResponse>;
31+
waitForResponse: (requestPredicate?: RequestPredicate) => Promise<ObservedGrpcCallResponse>;
4032
}

src/playwright/mock-grpc-unary.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,29 @@
1-
import { expect, Page } from "@playwright/test";
2-
import { GrpcResponse, grpcResponseToBuffer } from "../base";
3-
import { MockedGrpcCall, UnaryMethodDefinitionish } from "./interfaces";
4-
import { readGrpcRequest } from "./read-grpc-request";
1+
import { expect, Page } from '@playwright/test';
2+
import { GrpcResponse, grpcResponseToBuffer } from '../base';
3+
import { MockedGrpcCall, UnaryMethodDefinitionish } from './interfaces';
4+
import { readGrpcRequest } from './read-grpc-request';
55

66
export async function mockGrpcUnary(
77
page: Page,
88
rpc: UnaryMethodDefinitionish,
9-
response: GrpcResponse | ((request: Uint8Array | null) => GrpcResponse)
9+
response: GrpcResponse | ((request: Uint8Array | null) => GrpcResponse),
10+
mockAtContextLevel: boolean = false,
1011
): Promise<MockedGrpcCall> {
1112
const url = `/${rpc.service.serviceName}/${rpc.methodName}`;
1213

1314
// note this wildcard route url base is done in order to match both localhost and deployed service usages.
14-
await page.route("**" + url, (route) => {
15-
expect(
16-
route.request().method(),
17-
"ALL gRPC requests should be a POST request"
18-
).toBe("POST");
15+
await (mockAtContextLevel ? page.context() : page).route('**' + url, (route) => {
16+
expect(route.request().method(), 'ALL gRPC requests should be a POST request').toBe('POST');
1917

20-
const grpcResponse =
21-
typeof response === "function"
22-
? response(readGrpcRequest(route.request()))
23-
: response;
18+
const grpcResponse = typeof response === 'function' ? response(readGrpcRequest(route.request())) : response;
2419

2520
const grpcResponseBody = grpcResponseToBuffer(grpcResponse);
2621

2722
return route.fulfill({
2823
body: grpcResponseBody,
29-
contentType: "application/grpc-web+proto",
24+
contentType: 'application/grpc-web+proto',
3025
headers: {
31-
"Access-Control-Allow-Origin": "*",
26+
'Access-Control-Allow-Origin': '*',
3227
},
3328
});
3429
});

src/playwright/observe-grpc-unary.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
1-
import { Page } from "@playwright/test";
2-
import {
3-
ObservedGrpcCall,
4-
RequestPredicate,
5-
UnaryMethodDefinitionish,
6-
} from "./interfaces";
7-
import { decodeGrpcWebBody } from "../base";
8-
import { status as Status } from "@grpc/grpc-js";
9-
import { readGrpcRequest } from "./read-grpc-request";
10-
11-
export async function observeGrpcUnary(
12-
page: Page,
13-
rpc: UnaryMethodDefinitionish
14-
): Promise<ObservedGrpcCall> {
1+
import { Page } from '@playwright/test';
2+
import { ObservedGrpcCall, RequestPredicate, UnaryMethodDefinitionish } from './interfaces';
3+
import { decodeGrpcWebBody } from '../base';
4+
import { status as Status } from '@grpc/grpc-js';
5+
import { readGrpcRequest } from './read-grpc-request';
6+
7+
export async function observeGrpcUnary(page: Page, rpc: UnaryMethodDefinitionish): Promise<ObservedGrpcCall> {
158
const url = `/${rpc.service.serviceName}/${rpc.methodName}`;
169

1710
// note this wildcard route url base is done in order to match both localhost and deployed service usages.
1811
// eslint-disable-next-line @typescript-eslint/no-misused-promises
19-
await page.route("**" + url, async (route) => await route.continue());
12+
await page.route('**' + url, async (route) => await route.continue());
2013

2114
return {
2215
async waitForResponse(requestPredicate?: RequestPredicate) {
@@ -33,17 +26,15 @@ export async function observeGrpcUnary(
3326
return true;
3427
});
3528

36-
const response = await page.waitForResponse(
37-
(resp) => resp.request() === request
38-
);
29+
const response = await page.waitForResponse((resp) => resp.request() === request);
3930

4031
const requestMessage = readGrpcRequest(request);
4132

4233
const responseParsed = await decodeGrpcWebBody(await response.body());
4334

4435
const trailers = responseParsed.trailers ?? null;
4536

46-
if ("status" in responseParsed) {
37+
if ('status' in responseParsed) {
4738
return {
4839
requestMessage,
4940
responseMessage: null,

src/playwright/read-grpc-request.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Request } from "playwright-core";
2-
import { unframeRequest } from "../base";
1+
import { Request } from 'playwright-core';
2+
import { unframeRequest } from '../base';
33

44
export function readGrpcRequest(request: Request): Uint8Array | null {
55
const requestBody = request.postDataBuffer();

0 commit comments

Comments
 (0)