Skip to content

Commit 2f99f6b

Browse files
committed
0.0.11
1 parent fadfd0a commit 2f99f6b

File tree

8 files changed

+312
-11
lines changed

8 files changed

+312
-11
lines changed

README.md

Lines changed: 215 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,215 @@
1-
* A wrapper of Selenium-webdriver
2-
* A bridge to drive appium to support native app automation
3-
* Simple logic to wait for element to show up
4-
* Support PageObject pattern. Easy to wait for pages to load complete or navigate among pages
1+
# selenium-appium
2+
selenium-appium is [selenium-webdriver](https://seleniumhq.github.io/selenium/docs/api/javascript/) extension to make selenium-webdriver to drive [Appium](https://github.com/appium/appium) to run automation for native, hybrid and mobile web and desktop apps.
3+
4+
[![NPM version](https://badge.fury.io/js/selenium-appium.svg)](https://npmjs.org/package/selenium-appium)
5+
[![Monthly Downloads](https://img.shields.io/npm/dm/selenium-appium.svg)](https://npmjs.org/package/selenium-appium)
6+
7+
### Features
8+
1. A bridge to make selenium-webdriver to drive appium for native app automation. Implement [Mobile JSON Wire Protocol Specification](https://github.com/SeleniumHQ/mobile-spec/blob/master/spec-draft.md) locator which selenium-webdriver doesn't support.
9+
2. Supports PageObject Pattern for selenium-webdriver
10+
3. Supports iOS, Andriod, Windows which Appium supports.
11+
4. Supports Typescript
12+
5. Avoid webdriver.wait(until.elementLocated()).click() pattern and replaced it with By2.click()
13+
14+
### Installation
15+
```
16+
npm i selenium-appium --save-dev
17+
```
18+
19+
### By2
20+
- By2 is a subclass of selenium-webdriver.By. So you can use it anywhere which selenium-webdriver provides.
21+
- Also By2 implements the WebElement interface, so you can call click and other element operation directly.
22+
- No webdriver.wait needed. Instead of findElement, By2 itself loop until element is located before actual click happens.
23+
```
24+
test("By2 used in selenium-webdriver.WebDriver", async () => {
25+
const webdriver = await new Builder()
26+
.usingServer(url)
27+
.withCapabilities(capabilities)
28+
.build();
29+
const element = await webdriver.wait(until.elementLocated(By2.nativeName('One')));
30+
await element.click();
31+
await webdriver.quit();
32+
});
33+
34+
test("By2 Supports multiple webdriver2", async () => {
35+
const webdriver = new WebDriver2();
36+
await webdriver.startWithCapabilities(capabilities)
37+
await By2.nativeName('One', webdriver).click();
38+
await webdriver.quit();
39+
});
40+
41+
42+
test("By2 deduced WebDriver2 for single WebDriver2", async () => {
43+
await driver.startWithCapabilities(capabilities)
44+
await By2.nativeName('One').click();
45+
await driver.quit();
46+
});
47+
```
48+
49+
### driver
50+
- driver is a wrapper of WebDriver which selenium-webdriver provides.
51+
- Implements all functions of WebDriver of selenium-webdriver.
52+
- It provides the default driver for By2.
53+
- It delayed the creation of WebDriver, and implement the interface of WebDriver.
54+
55+
There are two ways to initialize the driver: startWithWebDriver or startWithWebDriver.
56+
57+
startWithWebDriver allows you to attach the driver to existing WebDriver if capability is not enough for your testing.
58+
59+
```
60+
test("simple webdriver2, and driver create from WebDriver", async () => {
61+
const webdriver = await new Builder()
62+
.usingServer(url)
63+
.withCapabilities(capabilities)
64+
.build();
65+
await driver.startWithWebDriver(webdriver);
66+
await By2.nativeName('One').click();
67+
await webdriver.quit();
68+
});
69+
70+
test("Multiple webdriver2", async () => {
71+
const webdriver = new WebDriver2();
72+
await webdriver.startWithCapabilities(capabilities)
73+
await By2.nativeName('One', webdriver).click();
74+
await webdriver.quit();
75+
});
76+
77+
78+
test("Simple Webdriver2, and Driver create from capabilities", async () => {
79+
await driver.startWithCapabilities(capabilities)
80+
await By2.nativeName('One').click();
81+
await driver.quit();
82+
});
83+
```
84+
85+
###
86+
[PageObject](https://github.com/SeleniumHQ/selenium/wiki/PageObjects) reduces the amount of duplicated code and easy to maintain.
87+
1. get in typescript would make you always get new instance of By2. For example, get resultTextBox()
88+
2. waitForPageLoaded pairs with isPageLoaded. You only need to overload isPageLoaded. waitForPageLoaded would poll until isPageLoaded is true or timeout.
89+
3. In practice, we only need simple instance of PageObject since it doesn't save state, and all state could be query by By2 from remote app.
90+
91+
PageObject
92+
```
93+
import { PageObject, By2 } from "selenium-appium";
94+
95+
class CalculatorPage extends PageObject {
96+
isPageLoaded() {
97+
return this.minusButton.isDisplayed();
98+
}
99+
100+
get resultTextBox() { return By2.nativeAccessibilityId('CalculatorResults');}
101+
get equalButton() { return By2.nativeAccessibilityId('equalButton'); }
102+
get clearButton() { return By2.nativeName('Clear'); }
103+
get plusButton() { return By2.nativeName('Plus'); }
104+
get divideButton() { return By2.nativeAccessibilityId('divideButton'); }
105+
get multiplyButton() { return By2.nativeXpath("//Button[@Name='Multiply by']") }
106+
get minusButton() { return By2.nativeXpath("//Button[@AutomationId=\"minusButton\"]"); }
107+
108+
109+
private async pressKeys(keys: string) {
110+
for (var key of keys) {
111+
await By2.nativeAccessibilityId('num' + key + 'Button').click();
112+
}
113+
}
114+
async divid(a: string, b: string): Promise<string> {
115+
await this.pressKeys(a);
116+
await this.divideButton.click();
117+
await this.pressKeys(b);
118+
await this.equalButton.click();
119+
return await this.getCalculatorResultText();
120+
}
121+
122+
async multiply(a: string, b: string): Promise<string> {
123+
await this.pressKeys(a);
124+
await this.multiplyButton.click();
125+
await this.pressKeys(b);
126+
await this.equalButton.click();
127+
return await this.getCalculatorResultText();
128+
}
129+
130+
async plus(a: string, b: string): Promise<string> {
131+
await this.pressKeys(a);
132+
await this.plusButton.click();
133+
await this.pressKeys(b);
134+
await this.equalButton.click();
135+
return await this.getCalculatorResultText();
136+
}
137+
138+
async minus(a: string, b: string): Promise<string> {
139+
await this.pressKeys(a);
140+
await this.minusButton.click();
141+
await this.pressKeys(b);
142+
await this.equalButton.click();
143+
return await this.getCalculatorResultText();
144+
}
145+
146+
private async getCalculatorResultText(): Promise<string> {
147+
return (await this.resultTextBox.getText()).replace('Display is', '').trim();
148+
}
149+
}
150+
151+
export default new CalculatorPage();
152+
```
153+
154+
use the PageObject
155+
```
156+
beforeEach(async () => {
157+
await CalculatorPage.waitForPageLoaded();
158+
await CalculatorPage.clearButton.clear();
159+
});
160+
test('Addition', async () => {
161+
// Find the buttons by their names and click them in sequence to perform 1 + 7 = 8
162+
expect(await CalculatorPage.plus('1', '7')).toBe('8');
163+
});
164+
165+
test('Division', async () => {
166+
// Find the buttons by their accessibility ids and click them in sequence to perform 88 / 11 = 8
167+
expect(await CalculatorPage.divid('88', '11')).toBe('8');
168+
});
169+
170+
test('Multiplication', async () => {
171+
// Find the buttons by their names using XPath and click them in sequence to perform 9 x 9 = 81
172+
expect(await CalculatorPage.multiply('9', '9')).toBe('81');
173+
});
174+
175+
test('Subtraction', async () => {
176+
// Find the buttons by their accessibility ids using XPath and click them in sequence to perform 9 - 1 = 8
177+
expect(await CalculatorPage.minus('9', '1')).toBe('8');
178+
});
179+
```
180+
181+
### Configuration
182+
There are two global timers: waitforPageTimeout and waitforTimeout.
183+
184+
waitforTimeout is used by By2. When call any WebElement function of By2, it loops until the element is located
185+
186+
waitforPageTimeout is used by PageObject. It defined the default timeout for waitForPageLoaded.
187+
188+
```
189+
Config.setWaitForPageTimeout(100);
190+
expect(Config.getWaitForPageTimeout()).toBe(100);
191+
```
192+
Config.setWaitForTimeout(100);
193+
expect(Config.getWaitForTimeout()).toBe(100);
194+
195+
### driver.seleniumDriver
196+
driver.seleniumDriver returns the instance of actual WebDriver.
197+
198+
### Note
199+
Because of typescript error, unlike By.name, By2 only replaced it with By2.name2.
200+
201+
### Why selenium-appium
202+
selenium-appium projected is created when I prototype automation for react-native Windows testing.
203+
[Appium](https://github.com/appium/appium) is an open source, cross-platform test automation tool for native, hybrid and mobile web and desktop apps. We support simulators (iOS), emulators (Android), and real devices (iOS, Android, Windows, Mac).
204+
205+
[Selenium](https://github.com/SeleniumHQ/selenium) is a browser automation library. Most often used for testing web-applications.
206+
207+
[selenium-webdriver](https://seleniumhq.github.io/selenium/docs/api/javascript/) is the offical WebDriver Javascript binding from selenium project.
208+
209+
Although [WebDriverIO](https://webdriver.io/) provides webdriver for Appium, it has some restrictions:
210+
1. Selenium has very large user base in browser automation, but [WebDriverIO API](https://webdriver.io/docs/api.html) is different from [selenium javascript API](https://seleniumhq.github.io/selenium/docs/api/javascript/)
211+
2. WebDriverIO has problem to support WinAppDriver, for example, https://github.com/webdriverio/webdriverio/pull/4369
212+
213+
214+
Unfortunately selenium-webdriver doesn't support [
215+
Mobile JSON Wire Protocol Specification](https://github.com/SeleniumHQ/mobile-spec/blob/master/spec-draft.md). And that's why selenium-appium project is created.

example/__tests__/By2.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { By2, driver, WebDriver2 } from 'selenium-appium'
2+
import { Builder, until } from 'selenium-webdriver';
3+
4+
jest.setTimeout(50000);
5+
6+
const capabilities = {
7+
browserName: '',
8+
platformName: 'windows',
9+
deviceName: 'WindowsPC',
10+
app: 'Microsoft.WindowsCalculator_8wekyb3d8bbwe!App'
11+
}
12+
13+
const url = 'http://localhost:4723/wd/hub'
14+
15+
describe('By2', () => {
16+
test("By2 used in selenium-webdriver.WebDriver", async () => {
17+
const webdriver = await new Builder()
18+
.usingServer(url)
19+
.withCapabilities(capabilities)
20+
.build();
21+
const element = await webdriver.wait(until.elementLocated(By2.nativeName('One')));
22+
await element.click();
23+
await webdriver.quit();
24+
});
25+
26+
test("By2 Supports multiple webdriver2", async () => {
27+
const webdriver = new WebDriver2();
28+
await webdriver.startWithCapabilities(capabilities)
29+
await By2.nativeName('One', webdriver).click();
30+
await webdriver.quit();
31+
});
32+
33+
34+
test("By2 deduced WebDriver2 for single WebDriver2", async () => {
35+
await driver.startWithCapabilities(capabilities)
36+
await By2.nativeName('One').click();
37+
await driver.quit();
38+
});
39+
})

example/__tests__/CalculatorWithPageObject.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ afterAll(() => {
2020
});
2121

2222

23-
describe('Addition', () => {
23+
describe('Calulator Test', () => {
2424
// Applies only to tests in this describe block
2525
beforeEach(async () => {
2626
await CalculatorPage.waitForPageLoaded();
@@ -41,8 +41,6 @@ describe('Addition', () => {
4141
expect(await CalculatorPage.multiply('9', '9')).toBe('81');
4242
});
4343

44-
45-
4644
test('Subtraction', async () => {
4745
// Find the buttons by their accessibility ids using XPath and click them in sequence to perform 9 - 1 = 8
4846
expect(await CalculatorPage.minus('9', '1')).toBe('8');

example/__tests__/Driver2.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { By2, driver, WebDriver2 } from 'selenium-appium'
2+
import { Builder, until } from 'selenium-webdriver';
3+
4+
jest.setTimeout(50000);
5+
6+
const capabilities = {
7+
browserName: '',
8+
platformName: 'windows',
9+
deviceName: 'WindowsPC',
10+
app: 'Microsoft.WindowsCalculator_8wekyb3d8bbwe!App'
11+
}
12+
13+
const url = 'http://localhost:4723/wd/hub'
14+
15+
describe('driver', () => {
16+
test("simple webdriver2, and driver create from WebDriver", async () => {
17+
const webdriver = await new Builder()
18+
.usingServer(url)
19+
.withCapabilities(capabilities)
20+
.build();
21+
await driver.startWithWebDriver(webdriver);
22+
await By2.nativeName('One').click();
23+
await webdriver.quit();
24+
});
25+
26+
test("Multiple webdriver2", async () => {
27+
const webdriver = new WebDriver2();
28+
await webdriver.startWithCapabilities(capabilities)
29+
await By2.nativeName('One', webdriver).click();
30+
await webdriver.quit();
31+
});
32+
33+
34+
test("Simple Webdriver2, and Driver create from capabilities", async () => {
35+
await driver.startWithCapabilities(capabilities)
36+
await By2.nativeName('One').click();
37+
await driver.quit();
38+
});
39+
})

example/__tests__/Times.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Config} from 'selenium-appium'
2+
3+
describe('Timeout example', () => {
4+
5+
// There are two global timers: waitforPageTimeout and waitforTimeout
6+
// waitforTimeout is used by By2. When call any WebElement function of By2, it loops until the element is located
7+
// waitforPageTimeout is used by PageObject. It defined the default timeout for waitForPageLoaded.
8+
9+
Config.setWaitForPageTimeout(100);
10+
expect(Config.getWaitForPageTimeout()).toBe(100);
11+
12+
Config.setWaitForTimeout(100);
13+
expect(Config.getWaitForTimeout()).toBe(100);
14+
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "selenium-appium",
3-
"version": "0.0.10",
3+
"version": "0.0.11",
44
"description": "A selenium-webdriver extension to support native app testing by Mobile JSON Wire Protocol which appium supports, also provides easy wait for element for pages or navigation among pages, and supports PageObject pattern",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/Config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ class Configuration {
1616
return this.waitforTimeout;
1717
}
1818

19-
setWaitforPageTimeout(timeout: number) {
19+
setWaitForPageTimeout(timeout: number) {
2020
this.waitforPageTimeout = timeout;
2121
}
22-
getWaitforPageTimeout(): number {
22+
getWaitForPageTimeout(): number {
2323
return this.waitforPageTimeout;
2424
}
2525
};

src/pageobject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export class PageObject {
2626
}
2727

2828
waitForPageLoaded(timeout?: number) {
29-
const theTimeout = timeout ? timeout : Config.getWaitforPageTimeout();
29+
const theTimeout = timeout ? timeout : Config.getWaitForPageTimeout();
3030
this.driver.wait2(
3131
() => {
3232
return this.isPageLoaded();

0 commit comments

Comments
 (0)