Skip to content
This repository was archived by the owner on Dec 29, 2022. It is now read-only.

Commit f3f81df

Browse files
committed
Call barriers one at a time, implement other feedback.
1 parent d0124f3 commit f3f81df

File tree

8 files changed

+141
-59
lines changed

8 files changed

+141
-59
lines changed

lib/blockingproxy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export class BlockingProxy implements WebDriverBarrier {
244244
return;
245245
}
246246

247-
this.proxy.requestListener(originalRequest, response);
247+
this.proxy.handleRequest(originalRequest, response);
248248
}
249249

250250
onCommand(command: WebDriverCommand): Promise<void> {

lib/webdriver_commands.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* Utilities for parsing WebDriver commands from HTTP Requests.
33
*/
44
import * as events from 'events';
5-
import * as http from 'http';
65

76
type HttpMethod = 'GET'|'POST'|'DELETE';
87
export type paramKey = 'sessionId' | 'elementId' | 'name' | 'propertyName';

lib/webdriver_proxy.ts

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import * as url from 'url';
44
import {parseWebDriverCommand, WebDriverCommand} from './webdriver_commands';
55

66
/**
7-
* A proxy that understands WebDriver commands. Users can add middleware (similar to middleware in
8-
* express) that will be called before
9-
* forwarding the request to WebDriver or forwarding the response to the client.
7+
* A proxy that understands WebDriver commands. Users can add barriers * (similar to middleware in
8+
* express) that will be called before forwarding the request to WebDriver. The proxy will wait for
9+
* each barrier to finish, calling them in the order in which they were added.
1010
*/
1111
export class WebDriverProxy {
1212
barriers: WebDriverBarrier[];
@@ -21,7 +21,7 @@ export class WebDriverProxy {
2121
this.barriers.push(barrier);
2222
}
2323

24-
requestListener(originalRequest: http.IncomingMessage, response: http.ServerResponse) {
24+
async handleRequest(originalRequest: http.IncomingMessage, response: http.ServerResponse) {
2525
let command = parseWebDriverCommand(originalRequest.url, originalRequest.method);
2626

2727
let replyWithError = (err) => {
@@ -30,47 +30,58 @@ export class WebDriverProxy {
3030
response.end();
3131
};
3232

33-
// TODO: What happens when barriers error? return a client error?
34-
let barrierPromises = this.barriers.map((b) => b.onCommand(command));
33+
// Process barriers in order, one at a time.
34+
try {
35+
for (let barrier of this.barriers) {
36+
await barrier.onCommand(command);
37+
}
38+
} catch (err) {
39+
replyWithError(err);
40+
// Don't call through if a barrier fails.
41+
return;
42+
}
3543

36-
Promise.all(barrierPromises).then(() => {
37-
let parsedUrl = url.parse(this.seleniumAddress);
38-
let options: http.RequestOptions = {};
39-
options.method = originalRequest.method;
40-
options.path = parsedUrl.path + originalRequest.url;
41-
options.hostname = parsedUrl.hostname;
42-
options.port = parseInt(parsedUrl.port);
43-
options.headers = originalRequest.headers;
44+
let parsedUrl = url.parse(this.seleniumAddress);
45+
let options: http.RequestOptions = {};
46+
options.method = originalRequest.method;
47+
options.path = parsedUrl.path + originalRequest.url;
48+
options.hostname = parsedUrl.hostname;
49+
options.port = parseInt(parsedUrl.port);
50+
options.headers = originalRequest.headers;
4451

45-
let forwardedRequest = http.request(options);
52+
let forwardedRequest = http.request(options);
4653

47-
// clang-format off
48-
let reqData = '';
49-
originalRequest.on('data', (d) => {
50-
reqData += d;
51-
forwardedRequest.write(d);
52-
}).on('end', () => {
53-
command.handleData(reqData);
54-
forwardedRequest.end();
55-
}).on('error', replyWithError);
56-
57-
forwardedRequest.on('response', (seleniumResponse) => {
58-
response.writeHead(seleniumResponse.statusCode, seleniumResponse.headers);
54+
// clang-format off
55+
let reqData = '';
56+
originalRequest.on('data', (d) => {
57+
reqData += d;
58+
forwardedRequest.write(d);
59+
}).on('end', () => {
60+
command.handleData(reqData);
61+
forwardedRequest.end();
62+
}).on('error', replyWithError);
5963

60-
let respData = '';
61-
seleniumResponse.on('data', (d) => {
62-
respData += d;
63-
response.write(d);
64-
}).on('end', () => {
65-
command.handleResponse(seleniumResponse.statusCode, respData);
66-
response.end();
67-
}).on('error', replyWithError);
64+
forwardedRequest.on('response', (seleniumResponse) => {
65+
response.writeHead(seleniumResponse.statusCode, seleniumResponse.headers);
6866

67+
let respData = '';
68+
seleniumResponse.on('data', (d) => {
69+
respData += d;
70+
response.write(d);
71+
}).on('end', () => {
72+
command.handleResponse(seleniumResponse.statusCode, respData);
73+
response.end();
6974
}).on('error', replyWithError);
70-
// clang-format on
7175

72-
}, replyWithError);
76+
}).on('error', replyWithError);
77+
// clang-format on
7378
}
7479
}
7580

76-
export interface WebDriverBarrier { onCommand(command: WebDriverCommand): Promise<void>; }
81+
/**
82+
* When the proxy receives a WebDriver command, it will call onCommand() for each of it's barriers.
83+
* Barriers may return a promise for the proxy to wait for before proceeding. If the promise is
84+
* rejected, the proxy will reply with an error code and the result of the promise and the command
85+
* will not be forwarded to Selenium.
86+
*/
87+
export interface WebDriverBarrier { onCommand(command: WebDriverCommand): Promise<void>; }

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"selenium-mock": "^0.1.5",
3939
"selenium-webdriver": "2.53.3",
4040
"ts-node": "^2.0.0",
41-
"tslint": "^4.0.2",
41+
"tslint": "^4.3.1",
4242
"tslint-eslint-rules": "^3.1.0",
4343
"typescript": "^2.0.3",
4444
"vrsource-tslint-rules": "^0.14.1",

spec/unit/proxy_spec.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,4 @@ describe('BlockingProxy', () => {
55
let proxy = new BlockingProxy(8111);
66
expect(proxy.waitEnabled).toBe(true);
77
});
8-
9-
it('should provide hooks when relaying commands',
10-
() => {
11-
12-
13-
});
148
});

spec/unit/webdriver_commands_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('WebDriver command parser', () => {
2222
proxy = new WebDriverProxy(`http://localhost:${mockPort}/wd/hub`);
2323
testBarrier = new TestBarrier;
2424
proxy.addBarrier(testBarrier);
25-
server = http.createServer(proxy.requestListener.bind(proxy));
25+
server = http.createServer(proxy.handleRequest.bind(proxy));
2626
server.listen(0);
2727
let port = server.address().port;
2828

spec/unit/webdriver_proxy_spec.ts

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as nock from 'nock';
22

3-
import {CommandName, WebDriverCommand} from '../../lib/webdriver_commands';
3+
import {WebDriverCommand} from '../../lib/webdriver_commands';
44
import {WebDriverProxy} from '../../lib/webdriver_proxy';
55

66
import {InMemoryReader, InMemoryWriter, TestBarrier} from './util';
@@ -9,7 +9,7 @@ describe('WebDriver Proxy', () => {
99
let proxy: WebDriverProxy;
1010

1111
beforeEach(() => {
12-
proxy = new WebDriverProxy(`http://localhost:4444/wd/hub`);
12+
proxy = new WebDriverProxy('http://test_webdriver_url/wd/hub');
1313
});
1414

1515
it('proxies to WebDriver', (done) => {
@@ -22,13 +22,13 @@ describe('WebDriver Proxy', () => {
2222

2323
let scope = nock(proxy.seleniumAddress).get('/session/sessionId/get').reply(200, responseData);
2424

25-
proxy.requestListener(req, resp);
25+
proxy.handleRequest(req, resp);
2626

2727
resp.onEnd((data) => {
2828
// Verify that all nock endpoints were called.
2929
expect(resp.writeHead.calls.first().args[0]).toBe(200);
3030
expect(data).toEqual(JSON.stringify(responseData));
31-
scope.isDone();
31+
scope.done();
3232
done();
3333
});
3434
});
@@ -60,11 +60,88 @@ describe('WebDriver Proxy', () => {
6060
});
6161

6262
proxy.addBarrier(barrier);
63-
proxy.requestListener(req, resp);
63+
proxy.handleRequest(req, resp);
6464

6565
resp.onEnd(() => {
6666
expect(barrierDone).toBeTruthy();
67-
scope.isDone();
67+
scope.done();
68+
done();
69+
});
70+
});
71+
72+
it('waits for multiple barriers in order', (done) => {
73+
const WD_URL = '/session/sessionId/url';
74+
75+
let req = new InMemoryReader() as any;
76+
let resp = new InMemoryWriter() as any;
77+
resp.writeHead = jasmine.createSpy('spy');
78+
req.url = WD_URL;
79+
req.method = 'POST';
80+
81+
let barrier1 = new TestBarrier();
82+
let barrier1Done = false;
83+
barrier1.onCommand = (): Promise<void> => {
84+
return new Promise<void>((res) => {
85+
setTimeout(() => {
86+
expect(barrier2Done).toBeFalsy();
87+
barrier1Done = true;
88+
res();
89+
}, 150);
90+
});
91+
};
92+
let barrier2 = new TestBarrier();
93+
let barrier2Done = false;
94+
barrier2.onCommand = (): Promise<void> => {
95+
return new Promise<void>((res) => {
96+
setTimeout(() => {
97+
expect(barrier1Done).toBeTruthy();
98+
barrier2Done = true;
99+
res();
100+
}, 50);
101+
});
102+
};
103+
104+
let scope = nock(proxy.seleniumAddress).post(WD_URL).reply(200);
105+
106+
proxy.addBarrier(barrier1);
107+
proxy.addBarrier(barrier2);
108+
proxy.handleRequest(req, resp);
109+
110+
resp.onEnd(() => {
111+
expect(barrier2Done).toBeTruthy();
112+
scope.done();
113+
done();
114+
});
115+
});
116+
117+
it('returns an error if a barrier fails', (done) => {
118+
const WD_URL = '/session/sessionId/url';
119+
120+
let req = new InMemoryReader() as any;
121+
let resp = new InMemoryWriter() as any;
122+
resp.writeHead = jasmine.createSpy('spy');
123+
req.url = WD_URL;
124+
req.method = 'GET';
125+
126+
let barrier = new TestBarrier();
127+
barrier.onCommand = (): Promise<void> => {
128+
return new Promise<void>((res, rej) => {
129+
rej('Barrier failed');
130+
});
131+
};
132+
133+
let scope = nock(proxy.seleniumAddress).get(WD_URL).reply(200);
134+
135+
proxy.addBarrier(barrier);
136+
proxy.handleRequest(req, resp);
137+
138+
resp.onEnd((respData) => {
139+
expect(resp.writeHead.calls.first().args[0]).toBe(500);
140+
expect(respData).toEqual('Barrier failed');
141+
142+
// Should not call the selenium server.
143+
expect(scope.isDone()).toBeFalsy();
144+
nock.cleanAll();
68145
done();
69146
});
70147
});
@@ -85,13 +162,13 @@ describe('WebDriver Proxy', () => {
85162
barrier.onCommand = (command: WebDriverCommand): Promise<void> => {
86163
command.on('response', () => {
87164
expect(command.responseData['url']).toEqual(RESPONSE.url);
88-
scope.isDone();
165+
scope.done();
89166
done();
90167
});
91168
return undefined;
92169
};
93170
proxy.addBarrier(barrier);
94-
proxy.requestListener(req, resp);
171+
proxy.handleRequest(req, resp);
95172
});
96173

97174
it('propagates http errors', (done) => {
@@ -106,13 +183,13 @@ describe('WebDriver Proxy', () => {
106183

107184
let scope = nock(proxy.seleniumAddress).post(WD_URL).replyWithError(ERR);
108185

109-
proxy.requestListener(req, resp);
186+
proxy.handleRequest(req, resp);
110187

111188
resp.onEnd((data) => {
112189
expect(resp.writeHead.calls.first().args[0]).toBe(500);
113190
expect(data).toEqual(ERR.toString());
114-
scope.isDone();
191+
scope.done();
115192
done();
116193
});
117194
});
118-
});
195+
});

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"target": "es6",
44
"module": "commonjs",
55
"moduleResolution": "node",
6+
"noUnusedLocals": true,
67
"sourceMap": true,
78
"declaration": true,
89
"removeComments": false,

0 commit comments

Comments
 (0)