Skip to content

Commit d410573

Browse files
panayot-cankovpanayot-cankov
andauthored
chore: apply webdriver code review (#27)
Co-authored-by: panayot-cankov <[email protected]>
1 parent ab300e7 commit d410573

File tree

2 files changed

+58
-23
lines changed

2 files changed

+58
-23
lines changed

examples/selenium-interop/w3schools.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe("w3schools", () => {
2929
await driver?.quit();
3030
});
3131

32-
test.skip("navigate to js statements page", async () => {
32+
test("navigate to js statements page", async () => {
3333
await driver.navigate().to('http://www.w3schools.com');
3434
await sleep(3000);
3535
await session.navigateTo('http://www.w3schools.com/js')

packages/@progress/roadkill/webdriver.ts

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export type Proxy = {
7171
* Lists the address for which the proxy should be bypassed when the proxyType is "manual".
7272
* A List containing any number of Strings.
7373
*/
74-
noProxy?: (number | string)[],
74+
noProxy?: string[],
7575
/**
7676
* Defines the proxy host for encrypted TLS traffic when the proxyType is "manual".
7777
* A host and optional port for scheme "https".
@@ -308,7 +308,7 @@ export interface Cookie {
308308
secure?: boolean;
309309
httpOnly?: boolean;
310310
expiry?: number;
311-
sameSite?: "Lax" | "Strict";
311+
sameSite?: "Lax" | "Strict" | "None";
312312
}
313313

314314
export type ElementLookup = CSSSelector | LinkText | PartialLinkText | TagName | XPathSelector;
@@ -416,7 +416,7 @@ export interface ActionSequencePointer {
416416
parameters?: {
417417
pointerType?: "mouse" | "pen" | "touch";
418418
};
419-
actions: (Actions.Pause | Actions.PointerUp | Actions.PointerDown | Actions.PointerMove)[];
419+
actions: (Actions.Pause | Actions.PointerUp | Actions.PointerDown | Actions.PointerMove | Actions.PointerCancel)[];
420420
}
421421

422422
export interface ActionSequenceWheel {
@@ -472,6 +472,19 @@ export interface WebDriverClientOptions {
472472
/**
473473
* A [Local end](https://www.w3.org/TR/webdriver2/#nodes) node implementation of the WebDriver specification.
474474
*/
475+
476+
// Helpers for safe JSON (de)serialization
477+
function withReplacer(serializer?: Serializer) {
478+
return typeof serializer?.serialize === "function"
479+
? (_k: string, v: any) => serializer!.serialize!(v)
480+
: undefined;
481+
}
482+
function withReviver(serializer?: Serializer) {
483+
return typeof serializer?.deserialize === "function"
484+
? (_k: string, v: any) => serializer!.deserialize!(v)
485+
: undefined;
486+
}
487+
475488
export class WebDriverClient {
476489

477490
public useImplicitSignal = true;
@@ -515,22 +528,44 @@ export class WebDriverClient {
515528

516529
public async request<Args, Result>(method: Method, uri: string, args?: Args, signal?: AbortSignal, serializer?: Serializer): Promise<Result> {
517530
try {
518-
const requestInit: RequestInit = {};
519-
requestInit.method = method;
531+
const headers: Record<string, string> = { "Accept": "application/json" };
532+
const requestInit: RequestInit = { method, headers };
520533
signal = withImplicitSignal(signal, this.useImplicitSignal);
521534
if (signal) requestInit.signal = signal;
522-
if (args !== undefined) requestInit.body = JSON.stringify(args, serializer?.serialize && ((key, value) => serializer.serialize(value)))
535+
if (args !== undefined) {
536+
headers["Content-Type"] = "application/json; charset=utf-8";
537+
requestInit.body = JSON.stringify(args, withReplacer(serializer));
538+
}
523539

524-
this.log(`fetch: ${method} ${uri}${args !== undefined ? " " + requestInit.body.toString().substring(0, 40) : ""}`);
540+
const bodyStr = typeof requestInit.body === "string" ? requestInit.body.slice(0, 40) : "";
541+
this.log(`fetch: ${method} ${uri}${bodyStr ? " " + bodyStr : ""}`);
525542
const response = await this.fetchImplementation(`${this.options.address}${uri}`, requestInit);
526543

527-
this.log(` response: ${method} ${response.status} ${response.statusText} ${uri}${args !== undefined ? " " + requestInit.body.toString().substring(0, 40) : ""}`);
544+
this.log(` response: ${method} ${response.status} ${response.statusText} ${uri}${bodyStr ? " " + bodyStr : ""}`);
545+
let text = "";
546+
try { text = await response.text(); } catch {}
547+
528548
if (!response.ok) {
529-
throw new WebDriverRequestError(await response.json() as ErrorResult);
549+
if (text) {
550+
try {
551+
const errJson = JSON.parse(text);
552+
throw new WebDriverRequestError(errJson as ErrorResult);
553+
} catch {
554+
throw new WebDriverRequestError(`${response.status} ${response.statusText}`);
555+
}
556+
}
557+
throw new WebDriverRequestError(`${response.status} ${response.statusText}`);
558+
}
559+
560+
if (!text) return undefined as unknown as Result;
561+
562+
let json: any;
563+
try {
564+
json = JSON.parse(text, withReviver(serializer));
565+
} catch {
566+
throw new WebDriverRequestError("Invalid JSON from WebDriver endpoint.");
530567
}
531568

532-
const text = await response.text()
533-
const json = JSON.parse(text, serializer?.deserialize && ((key, value) => serializer.deserialize(value)));
534569
const result = (json as { value: Result }).value;
535570
return result;
536571
} catch(cause) {
@@ -611,7 +646,7 @@ export class Session implements Disposable, Serializer {
611646
*/
612647
public async setTimeouts(timeouts: TimeoutsConfiguration, signal?: AbortSignal): Promise<void> {
613648
try {
614-
return this.request("POST", `/session/${this.sessionId}/timeouts`, timeouts, signal);
649+
return await this.request("POST", `/session/${this.sessionId}/timeouts`, timeouts, signal);
615650
} catch(cause) {
616651
throw new WebDriverMethodError(`Failed to set timeouts.`, { cause }, { timeouts });
617652
}
@@ -741,7 +776,7 @@ export class Session implements Disposable, Serializer {
741776
const res = await this.request<{ type: "tab" | "window" }, { handle: WindowHandle, type: "tab" | "window"}>("POST", `/session/${this.sessionId}/window/new`, { type }, signal);
742777
return new Window(this, res.handle, res.type);
743778
} catch(cause) {
744-
throw new WebDriverMethodError(`Failed open a new window.`, { cause }, { type });
779+
throw new WebDriverMethodError(`Failed to open a new window.`, { cause }, { type });
745780
}
746781
}
747782

@@ -874,16 +909,16 @@ export class Session implements Disposable, Serializer {
874909
*
875910
* The ***Get Page Source*** command returns a string serialization of the DOM of the current browsing context active document.
876911
*/
877-
public getPageSource(signal?: AbortSignal): Promise<string> {
912+
public async getPageSource(signal?: AbortSignal): Promise<string> {
878913
try {
879-
return this.request("GET", `/session/${this.sessionId}/source`, undefined, signal);
914+
return await this.request("GET", `/session/${this.sessionId}/source`, undefined, signal);
880915
} catch(cause) {
881916
throw new WebDriverMethodError(`Failed to get page source.`, { cause });
882917
}
883918
}
884919

885920
/**
886-
* [13.2.1 Execute Scrip](https://www.w3.org/TR/webdriver2/#execute-script)
921+
* [13.2.1 Execute Script](https://www.w3.org/TR/webdriver2/#execute-script)
887922
*/
888923
public async executeScript(script: string, signal?: AbortSignal, ...args: any[]): Promise<any> {
889924
try {
@@ -896,9 +931,9 @@ export class Session implements Disposable, Serializer {
896931
/**
897932
* [13.2.2 Execute Async Script](https://www.w3.org/TR/webdriver2/#execute-async-script)
898933
*/
899-
public executeScriptAsync(script: string, signal?: AbortSignal, ...args: any[]): Promise<any> {
934+
public async executeScriptAsync(script: string, signal?: AbortSignal, ...args: any[]): Promise<any> {
900935
try {
901-
return this.request("POST", `/session/${this.sessionId}/execute/async`, { script, args }, signal);
936+
return await this.request("POST", `/session/${this.sessionId}/execute/async`, { script, args }, signal);
902937
} catch(cause) {
903938
throw new WebDriverMethodError(`Failed to execute script async.`, { cause });
904939
}
@@ -1199,9 +1234,9 @@ export class Element implements WebElementReference {
11991234
/**
12001235
* [12.4.3 Get Element Property](https://www.w3.org/TR/webdriver2/#get-element-property)
12011236
*/
1202-
public async getProperty(name: string, signal?: AbortSignal): Promise<null | string> {
1237+
public async getProperty(name: string, signal?: AbortSignal): Promise<any> {
12031238
try {
1204-
return await this.request<{}, null | string>("GET", `/session/${this.sessionId}/element/${this.elementId}/property/${name}`, undefined, signal);
1239+
return await this.request<{}, any>("GET", `/session/${this.sessionId}/element/${this.elementId}/property/${name}`, undefined, signal);
12051240
} catch(cause) {
12061241
throw new WebDriverMethodError(`Failed to get property ${name} from element.`, { cause }, { name });
12071242
}
@@ -1257,7 +1292,7 @@ export class Element implements WebElementReference {
12571292
try {
12581293
return await this.request<{}, ElementRect>("GET", `/session/${this.sessionId}/element/${this.elementId}/rect`, undefined, signal);
12591294
} catch(cause) {
1260-
throw new WebDriverMethodError(`Failed to get tag name from element.`, { cause });
1295+
throw new WebDriverMethodError(`Failed to get rect from element.`, { cause });
12611296
}
12621297
}
12631298

@@ -1395,4 +1430,4 @@ export class ShadowRoot implements ShadowRootReference {
13951430
throw error;
13961431
});
13971432
}
1398-
}
1433+
}

0 commit comments

Comments
 (0)