Skip to content

Commit ac950e5

Browse files
authored
feat: tests for playground samples used in docs (#63)
New: - Tests for playground samples used in docs - Introduce new `run_preview.py` to run tests based on preview feature (it copy preview apps in sut folder once before tests) Refactor: - Chrome: Add implicit wait of 60sec - Make `run_adb_command` and `run_simctl_command` public - Preview.run_url() now takes DeviceInfo object as param (instead of id and platform) - Escape playground url now happens in `Preview.run_url()` instead of `Preview.get_url()` Fix: - Retry `xcrun simctl install` in case of failure (known issue of Xcode 10+)
1 parent 2cb2535 commit ac950e5

File tree

9 files changed

+164
-46
lines changed

9 files changed

+164
-46
lines changed

core/utils/chrome.py

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def __init__(self, kill_old=True):
1717
path = ChromeDriverManager().install()
1818
Log.info('Starting Google Chrome ...')
1919
self.driver = webdriver.Chrome(executable_path=path)
20+
self.driver.implicitly_wait(60)
2021
Log.info('Google Chrome started!')
2122

2223
def open(self, url):

core/utils/device/adb.py

+18-18
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
class Adb(object):
1717
@staticmethod
18-
def __run_adb_command(command, device_id=None, wait=True, timeout=60, fail_safe=False, log_level=logging.DEBUG):
18+
def run_adb_command(command, device_id=None, wait=True, timeout=60, fail_safe=False, log_level=logging.DEBUG):
1919
if device_id is None:
2020
command = '{0} {1}'.format(ADB_PATH, command)
2121
else:
@@ -28,7 +28,7 @@ def __get_ids(include_emulator=False):
2828
Get IDs of available android devices.
2929
"""
3030
devices = []
31-
output = Adb.__run_adb_command('devices -l').output
31+
output = Adb.run_adb_command('devices -l').output
3232
# Example output:
3333
# emulator-5554 device product:sdk_x86 model:Android_SDK_built_for_x86 device:generic_x86
3434
# HT46BWM02644 device usb:336592896X product:m8_google model:HTC_One_M8 device:htc_m8
@@ -42,9 +42,9 @@ def __get_ids(include_emulator=False):
4242
@staticmethod
4343
def restart():
4444
Log.info("Restart adb.")
45-
Adb.__run_adb_command('kill-server')
45+
Adb.run_adb_command('kill-server')
4646
Process.kill(proc_name='adb')
47-
Adb.__run_adb_command('start-server')
47+
Adb.run_adb_command('start-server')
4848

4949
@staticmethod
5050
def get_devices(include_emulators=False):
@@ -63,7 +63,7 @@ def is_running(device_id):
6363
command = "shell dumpsys window windows | findstr mCurrentFocus"
6464
else:
6565
command = "shell dumpsys window windows | grep -E 'mCurrentFocus'"
66-
result = Adb.__run_adb_command(command=command, device_id=device_id, timeout=10, fail_safe=True)
66+
result = Adb.run_adb_command(command=command, device_id=device_id, timeout=10, fail_safe=True)
6767
return bool('Window' in result.output)
6868

6969
@staticmethod
@@ -87,7 +87,7 @@ def wait_until_boot(device_id, timeout=180, check_interval=3):
8787

8888
@staticmethod
8989
def reboot(device_id):
90-
Adb.__run_adb_command(command='reboot', device_id=device_id)
90+
Adb.run_adb_command(command='reboot', device_id=device_id)
9191
Adb.wait_until_boot(device_id=device_id)
9292

9393
@staticmethod
@@ -96,18 +96,18 @@ def prevent_screen_lock(device_id):
9696
Disable screen lock after time of inactivity.
9797
:param device_id: Device identifier.
9898
"""
99-
Adb.__run_adb_command(command='shell settings put system screen_off_timeout -1', device_id=device_id)
99+
Adb.run_adb_command(command='shell settings put system screen_off_timeout -1', device_id=device_id)
100100

101101
@staticmethod
102102
def pull(device_id, source, target):
103-
return Adb.__run_adb_command(command='pull {0} {1}'.format(source, target), device_id=device_id)
103+
return Adb.run_adb_command(command='pull {0} {1}'.format(source, target), device_id=device_id)
104104

105105
@staticmethod
106106
def get_page_source(device_id):
107107
temp_file = os.path.join(Settings.TEST_OUT_HOME, 'window_dump.xml')
108108
File.delete(temp_file)
109-
Adb.__run_adb_command(command='shell rm /sdcard/window_dump.xml', device_id=device_id)
110-
result = Adb.__run_adb_command(command='shell uiautomator dump', device_id=device_id)
109+
Adb.run_adb_command(command='shell rm /sdcard/window_dump.xml', device_id=device_id)
110+
result = Adb.run_adb_command(command='shell uiautomator dump', device_id=device_id)
111111
if 'UI hierchary dumped to' in result.output:
112112
time.sleep(1)
113113
Adb.pull(device_id=device_id, source='/sdcard/window_dump.xml', target=temp_file)
@@ -145,20 +145,20 @@ def is_text_visible(device_id, text, case_sensitive=False):
145145
def get_screen(device_id, file_path):
146146
File.delete(path=file_path)
147147
if Settings.HOST_OS == OSType.WINDOWS:
148-
Adb.__run_adb_command(command='exec-out screencap -p > ' + file_path,
149-
device_id=device_id,
150-
log_level=logging.DEBUG)
148+
Adb.run_adb_command(command='exec-out screencap -p > ' + file_path,
149+
device_id=device_id,
150+
log_level=logging.DEBUG)
151151
else:
152-
Adb.__run_adb_command(command="shell screencap -p | perl -pe 's/\\x0D\\x0A/\\x0A/g' > " + file_path,
153-
device_id=device_id)
152+
Adb.run_adb_command(command="shell screencap -p | perl -pe 's/\\x0D\\x0A/\\x0A/g' > " + file_path,
153+
device_id=device_id)
154154
if File.exists(file_path):
155155
return
156156
else:
157157
raise Exception('Failed to get screen of {0}.'.format(device_id))
158158

159159
@staticmethod
160160
def get_device_version(device_id):
161-
result = Adb.__run_adb_command(command='shell getprop ro.build.version.release', device_id=device_id)
161+
result = Adb.run_adb_command(command='shell getprop ro.build.version.release', device_id=device_id)
162162
if result.exit_code == 0:
163163
return result.output
164164
else:
@@ -167,7 +167,7 @@ def get_device_version(device_id):
167167
@staticmethod
168168
def open_home(device_id):
169169
cmd = 'shell am start -a android.intent.action.MAIN -c android.intent.category.HOME'
170-
Adb.__run_adb_command(command=cmd, device_id=device_id)
170+
Adb.run_adb_command(command=cmd, device_id=device_id)
171171
Log.info('Open home screen of {0}.'.format(str(device_id)))
172172

173173
@staticmethod
@@ -177,6 +177,6 @@ def install(apk_path, device_id):
177177
:param apk_path: File path to .apk.
178178
:param device_id: Device id.
179179
"""
180-
result = Adb.__run_adb_command(command='-s {0} install -r {1}'.format(device_id, apk_path), timeout=60)
180+
result = Adb.run_adb_command(command='-s {0} install -r {1}'.format(device_id, apk_path), timeout=60)
181181
assert 'Success' in result.output, 'Failed to install {0}. Output: {1}'.format(apk_path, result.output)
182182
Log.info('{0} installed successfully on {1}.'.format(apk_path, device_id))

core/utils/device/device.py

+6
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ def get_text(self):
7676
return text
7777

7878
def wait_for_text(self, text, timeout=60, retry_delay=1):
79+
"""
80+
Wait until text is visible on device.
81+
:param text: Text as string.
82+
:param timeout: Timeout in seconds.
83+
:param retry_delay: Retry interval in seconds.
84+
"""
7985
t_end = time.time() + timeout
8086
found = False
8187
error_msg = '{0} NOT found on {1}.'.format(text, self.name)

core/utils/device/simctl.py

+18-13
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
class Simctl(object):
1212

1313
@staticmethod
14-
def __run_simctl_command(command, wait=True, timeout=60):
14+
def run_simctl_command(command, wait=True, timeout=60):
1515
command = '{0} {1}'.format('xcrun simctl', command)
1616
return run(cmd=command, wait=wait, timeout=timeout)
1717

1818
# noinspection PyBroadException
1919
@staticmethod
2020
def __get_simulators():
21-
result = Simctl.__run_simctl_command(command='list --json devices')
21+
result = Simctl.run_simctl_command(command='list --json devices')
2222
try:
2323
return json.loads(result.output)
2424
except ValueError:
@@ -28,7 +28,7 @@ def __get_simulators():
2828
@staticmethod
2929
def start(simulator_info):
3030
if simulator_info.id is not None:
31-
Simctl.__run_simctl_command(command='boot {0}'.format(simulator_info.id))
31+
Simctl.run_simctl_command(command='boot {0}'.format(simulator_info.id))
3232
Simctl.wait_until_boot(simulator_info)
3333
return simulator_info
3434
else:
@@ -43,7 +43,7 @@ def is_running(simulator_info):
4343
simulator_info.id = str(sim['udid'])
4444
command = 'spawn {0} launchctl print system | grep com.apple.springboard.services'.format(
4545
simulator_info.id)
46-
service_state = Simctl.__run_simctl_command(command=command)
46+
service_state = Simctl.run_simctl_command(command=command)
4747
if "M A com.apple.springboard.services" in service_state.output:
4848
Log.info('Simulator "{0}" booted.'.format(simulator_info.name))
4949
return simulator_info
@@ -78,7 +78,7 @@ def is_available(simulator_info):
7878

7979
@staticmethod
8080
def stop_application(simulator_info, app_id):
81-
return Simctl.__run_simctl_command('terminate {0} {1}'.format(simulator_info.id, app_id))
81+
return Simctl.run_simctl_command('terminate {0} {1}'.format(simulator_info.id, app_id))
8282

8383
@staticmethod
8484
def stop_all(simulator_info):
@@ -87,14 +87,19 @@ def stop_all(simulator_info):
8787

8888
@staticmethod
8989
def install(simulator_info, path):
90-
result = Simctl.__run_simctl_command('install {0} {1}'.format(simulator_info.id, path))
91-
assert result.exit_code == 0, 'Failed to install {0} on {1}'.format(path, simulator_info.name)
92-
assert 'Failed to install the requested application' not in result.output, \
93-
'Failed to install {0} on {1}'.format(path, simulator_info.name)
90+
result = Simctl.run_simctl_command('install {0} {1}'.format(simulator_info.id, path))
91+
if result.exit_code != 0:
92+
# Since Xcode 10 sometimes xcrun simctl install fails first time (usually with iPhone X* devices).
93+
Log.info('Failed to install {0} on {1}.'.format(path, simulator_info.name))
94+
Log.info('Retry...')
95+
result = Simctl.run_simctl_command('install {0} {1}'.format(simulator_info.id, path))
96+
assert result.exit_code == 0, 'Failed to install {0} on {1}'.format(path, simulator_info.name)
97+
assert 'Failed to install the requested application' not in result.output, \
98+
'Failed to install {0} on {1}'.format(path, simulator_info.name)
9499

95100
@staticmethod
96101
def uninstall(simulator_info, app_id):
97-
result = Simctl.__run_simctl_command('uninstall {0} {1}'.format(simulator_info.id, app_id))
102+
result = Simctl.run_simctl_command('uninstall {0} {1}'.format(simulator_info.id, app_id))
98103
assert result.exit_code == 0, 'Failed to uninstall {0} on {1}'.format(app_id, simulator_info.name)
99104
assert 'Failed to uninstall the requested application' not in result.output, \
100105
'Failed to uninstall {0} on {1}'.format(app_id, simulator_info.name)
@@ -109,19 +114,19 @@ def uninstall_all(simulator_info):
109114
@staticmethod
110115
def get_screen(sim_id, file_path):
111116
File.delete(file_path)
112-
result = Simctl.__run_simctl_command('io {0} screenshot {1}'.format(sim_id, file_path))
117+
result = Simctl.run_simctl_command('io {0} screenshot {1}'.format(sim_id, file_path))
113118
assert result.exit_code == 0, 'Failed to get screenshot of {0}'.format(sim_id)
114119
assert File.exists(file_path), 'Failed to get screenshot of {0}'.format(sim_id)
115120

116121
@staticmethod
117122
def erase(simulator_info):
118-
result = Simctl.__run_simctl_command('erase {0}'.format(simulator_info.id))
123+
result = Simctl.run_simctl_command('erase {0}'.format(simulator_info.id))
119124
assert result.exit_code == 0, 'Failed to erase {0}'.format(simulator_info.name)
120125
Log.info('Erase {0}.'.format(simulator_info.name))
121126

122127
@staticmethod
123128
def erase_all():
124-
result = Simctl.__run_simctl_command('erase all')
129+
result = Simctl.run_simctl_command('erase all')
125130
assert result.exit_code == 0, 'Failed to erase all iOS Simulators.'
126131
Log.info('Erase all iOS Simulators.')
127132

core_tests/unit/product/test_preview_helpers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class PreviewHelperTests(unittest.TestCase):
1212
def test_01_constants(self):
1313
text = File.read(path=os.path.join(self.current_folder, 'preview.log'))
1414
url = Preview.get_url(output=text)
15-
assert 'nsplay://boot\\?instanceId=' in url
15+
assert 'nsplay://boot?instanceId=' in url
1616

1717

1818
if __name__ == '__main__':

products/nativescript/preview_helpers.py

+24-13
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import os
22
import re
33
import time
4+
5+
from core.enums.device_type import DeviceType
46
from core.enums.platform_type import Platform
7+
from core.log.log import Log
58
from core.settings import Settings
69
from core.settings.Settings import TEST_SUT_HOME, TEST_RUN_HOME
7-
from core.utils.device.adb import Adb, ADB_PATH
10+
from core.utils.device.adb import Adb
811
from core.utils.device.simctl import Simctl
912
from core.utils.file_utils import File
1013
from core.utils.run import run
@@ -61,6 +64,8 @@ def get_url(output):
6164
"""
6265
Get preview URL form tns log.
6366
This is the url you need to load in Preview app in order to see and sync your project.
67+
:param output: Output of `tns preview` command.
68+
:return: Playground url.
6469
"""
6570
url = re.findall(r"(nsplay[^\s']+)", output)[0]
6671
if Settings.PYTHON_VERSION < 3:
@@ -69,24 +74,30 @@ def get_url(output):
6974
else:
7075
from urllib.parse import unquote
7176
url = unquote(url, 'UTF-8')
72-
url = url.replace(r'?', r'\?')
73-
url = url.replace(r'&', r'\&')
7477
return url
7578

7679
@staticmethod
77-
def run_url(url, device_id, platform):
80+
def run_url(url, device):
7881
"""
79-
Runs your project in the Preview App on simulator or emulator
82+
Runs project in the Preview App.
83+
:param url: Playground url.
84+
:param device: DeviceInfo object.
8085
"""
81-
if platform is Platform.IOS:
82-
cmd = "xcrun simctl openurl {0} {1}.".format(device_id, url)
83-
result = run(cmd)
86+
# Url needs to be escaped before open with adb or simctl
87+
url = url.replace(r'?', r'\?')
88+
url = url.replace(r'&', r'\&')
89+
90+
# Run url
91+
Log.info('Open "{0}" on {1}.'.format(url, device.name))
92+
if device.type == DeviceType.EMU or device.type == DeviceType.ANDROID:
93+
cmd = 'shell am start -a android.intent.action.VIEW -d "{0}" org.nativescript.preview'.format(url)
94+
result = Adb.run_adb_command(command=cmd, device_id=device.id)
8495
assert 'error' not in result.output
85-
elif platform is Platform.ANDROID:
86-
cmd = '{0} -s {1} shell am start -a android.intent.action.VIEW -d "{2}" org.nativescript.preview' \
87-
.format(ADB_PATH, device_id, url)
88-
result = run(cmd)
96+
elif device.type == DeviceType.SIM:
97+
result = Simctl.run_simctl_command(command='openurl {0} {1}.'.format(device.id, url))
8998
assert 'error' not in result.output
99+
else:
100+
raise NotImplementedError('Open url not implemented for real iOS devices.')
90101

91102
@staticmethod
92103
def dismiss_simulator_alert():
@@ -104,7 +115,7 @@ def run_app(app_name, platform, device, bundle=False, hmr=False, uglify=False, a
104115
# Read the log and extract the url to load the app on emulator
105116
log = File.read(result.log_file)
106117
url = Preview.get_url(log)
107-
Preview.run_url(url, device.id, platform)
118+
Preview.run_url(url=url, device=device)
108119
# When you run preview on ios simulator on first run confirmation dialog is showh. This script will dismiss it
109120
if platform == Platform.IOS:
110121
time.sleep(2)

run_common.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from core.utils.gradle import Gradle
1212
from core.utils.npm import Npm
1313
from data.templates import Template
14+
from products.nativescript.preview_helpers import Preview
1415
from products.nativescript.tns import Tns
1516

1617

@@ -129,7 +130,7 @@ def __install_schematics():
129130
Npm.install(package=Settings.Packages.NS_SCHEMATICS, folder=Settings.TEST_RUN_HOME)
130131

131132

132-
def prepare(clone_templates=True, install_ng_cli=False):
133+
def prepare(clone_templates=True, install_ng_cli=False, get_preivew_packages=False):
133134
Log.info('================== Prepare Test Run ==================')
134135
__cleanup()
135136
__install_ns_cli()
@@ -139,5 +140,7 @@ def prepare(clone_templates=True, install_ng_cli=False):
139140
__install_schematics()
140141
if clone_templates:
141142
__get_templates()
143+
if get_preivew_packages:
144+
Preview.get_app_packages()
142145

143146
Log.settings()

run_preview.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import sys
2+
3+
import nose
4+
5+
import run_common
6+
from core.log.log import Log
7+
8+
if __name__ == '__main__':
9+
run_common.prepare(clone_templates=False, install_ng_cli=False, get_preivew_packages=True)
10+
Log.info("Running tests...")
11+
arguments = ['nosetests', '-v', '-s', '--nologcapture', '--logging-filter=nose', '--with-xunit', '--with-flaky']
12+
for i in sys.argv:
13+
arguments.append(str(i))
14+
nose.run(argv=arguments)

0 commit comments

Comments
 (0)