This repository has been archived by the owner on May 8, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 42
/
utils.ts
231 lines (208 loc) · 7.46 KB
/
utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
import {MobileConfig} from '../configs/wdio.shared.conf';
const getTextOfElement = async (
element: WebdriverIO.Element,
): Promise<string> => {
let visualText = '';
try {
// Android doesn't hold the text on the parent element
// so each text view in the parent needs to be checked
if (driver.isAndroid) {
const elements = await element.$$('*//android.widget.TextView');
for (let elm of elements) {
visualText = `${visualText} ${await elm.getText()}`;
}
} else {
visualText = await element.getText();
}
} catch (e) {
visualText = await element.getText();
}
return visualText.trim();
};
const locatorStrategy = (selector: string): string => {
return driver.isIOS ? `id=${selector}` : `//*[@content-desc="${selector}"]`;
};
const restartApp = async (): Promise<void> => {
if (!(driver.config as MobileConfig).firstAppStart) {
await driver.reset();
}
// Set the firstAppstart to false to say that the following test can be reset
(driver.config as MobileConfig).firstAppStart = false;
// Wait for the app to be ready and reset the state by clicking on the header image
const headerImage = await $(locatorStrategy('longpress reset app'));
await headerImage.waitForDisplayed();
if (driver.isIOS) {
return driver.execute('mobile: touchAndHold', {
elementId: headerImage.elementId,
duration: 1,
});
}
await driver.execute('mobile: longClickGesture', {
elementId: headerImage.elementId,
duration: 1000,
});
};
const hideKeyboard = async (): Promise<void> => {
// The hideKeyboard is not working on ios devices, so take a different approach
if (!(await driver.isKeyboardShown())) {
return;
}
if (driver.isIOS) {
await $('id=Return').click();
} else {
try {
await driver.hideKeyboard('pressKey', 'Done');
} catch (e) {
// Fallback
await driver.back();
}
}
// Wait for the keyboard animation to be done
await driver.pause(750);
};
const hideNumericKeyboard = async (): Promise<void> => {
// The hideKeyboard is not working on ios devices, so take a different approach
if (!(await driver.isKeyboardShown())) {
return;
}
if (driver.isIOS) {
await driver.execute('mobile: tap', {
element: await $('id=1'),
x: 0,
y: -100,
});
} else {
try {
await driver.hideKeyboard('pressKey', 'Done');
} catch (e) {
// Fallback
await driver.back();
}
}
// Wait for the keyboard animation to be done
await driver.pause(750);
};
const openDeepLinkUrl = async (url: string): Promise<void | string> => {
const prefix = 'mydemoapprn://';
if (driver.isAndroid) {
// Life is so much easier
return driver.execute('mobile:deepLink', {
url: `${prefix}${url}`,
package: 'com.saucelabs.mydemoapp.rn',
});
}
// We can use `driver.url` on iOS simulators, but not on iOS real devices. The reason is that iOS real devices
// open Siri when you call `driver.url('')` to use a deep link. This means that real devices need to have a different implementation
// then iOS sims
// iOS sims and real devices can be distinguished by their UDID. Based on these sources there is a diff in the UDIDS
// - https://blog.diawi.com/2018/10/15/2018-apple-devices-and-their-new-udid-format/
// - https://www.theiphonewiki.com/wiki/UDID
// iOS sims have more than 1 `-` in the UDID and the UDID is being
const simulatorRegex = new RegExp('(.*-.*){2,}');
// Check if we are a simulator
if (
'udid' in driver.capabilities &&
simulatorRegex.test(driver.capabilities.udid as string)
) {
await driver.url(`${prefix}${url}`);
} else {
// Else we are a real device and we need to take some extra steps
// Launch Safari to open the deep link
await driver.execute('mobile: launchApp', {
bundleId: 'com.apple.mobilesafari',
});
// Add the deep link url in Safari in the `URL`-field
// This can be 2 different elements, or the button, or the text field
// Use the predicate string because the accessibility label will return 2 different types
// of elements making it flaky to use. With predicate string we can be more precise
const addressBarSelector =
"name CONTAINS 'URL' OR name CONTAINS 'TabBarItemTitle' OR value contains 'Search or enter website name'";
const urlFieldSelector =
'type == "XCUIElementTypeTextField" && name CONTAINS "URL"';
const addressBar = $(`-ios predicate string:${addressBarSelector}`);
const urlField = $(`-ios predicate string:${urlFieldSelector}`);
// Wait for the url button to appear and click on it so the text field will appear
// iOS 13 now has the keyboard open by default because the URL field has focus when opening the Safari browser
if (!(await driver.isKeyboardShown())) {
await addressBar.waitForDisplayed();
await addressBar.click();
}
// Submit the url and add a break
await urlField.setValue(`${prefix}${url}\uE007`);
}
// Wait for the notification and accept it
// When using an iOS simulator you will only get the pop-up once, all the other times it won't be shown
try {
const openSelector =
"type == 'XCUIElementTypeButton' && name CONTAINS 'Open'";
const openButton = $(`-ios predicate string:${openSelector}`);
// Assumption is made that the alert will be seen within 2 seconds, if not it did not appear
await openButton.waitForDisplayed({timeout: 3000});
await openButton.click();
} catch (e) {
// ignore
console.log('Deeplink error = ', e);
}
};
/**
* Get the app state for iOS, see
* http://appium.io/docs/en/writing-running-appium/ios/ios-xctest-mobile-apps-management/
* 0: 'The current application state cannot be determined/is unknown',
* 1: 'The application is not running',
* 2: 'The application is running in the background and is suspended',
* 3: 'The application is running in the background and is not suspended',
* 4: 'The application is running in the foreground',
*/
const getIosAppState = (bundleId: string): Promise<number> => {
return driver.execute('mobile: queryAppState', {bundleId: bundleId});
};
/**
* Check if the application is running in the foreground
*/
const isIosApplicationRunning = async (bundleId: string): Promise<boolean> => {
try {
await driver.waitUntil(async () => (await getIosAppState(bundleId)) === 4);
return true;
} catch (e) {
return false;
}
};
/**
* Verify that the apps main activity is not open anymore, this means that the browser, or the choose browser
* helper is opened
*/
const androidBrowserOpened = async (): Promise<boolean> => {
try {
await driver.waitUntil(
async () =>
!(await driver.getCurrentActivity()).includes('.MainActivity') &&
!(
await driver.getCurrentActivity()
).includes('.GrantPermissionsActivity'),
);
return true;
} catch (e) {
return false;
}
};
/**
* Verify that the browser is opened.
* - iOS: For iOS we can check if Safari is running in the foreground
* - Android: For Android we can check the current activity. If it holds a browser reference we know
* for sure that the app is put on the background and that for example chrome is opened.
*/
const isBrowserOpened = async (): Promise<boolean> => {
if (driver.isIOS) {
return isIosApplicationRunning('com.apple.mobilesafari');
}
return androidBrowserOpened();
};
export {
getTextOfElement,
hideKeyboard,
hideNumericKeyboard,
isBrowserOpened,
locatorStrategy,
openDeepLinkUrl,
restartApp,
};