def get_screen(self, path, log_level=logging.INFO):
        """
        Save screen of mobile device.
        :param path: Path to image that will be saved.
        :param log_level: Log level.
        """

        # Ensure folder to save the screen exists
        File.delete(path)
        Folder.create(folder=os.path.dirname(path))

        if self.type is DeviceType.EMU or self.type is DeviceType.ANDROID:
            Adb.get_screen(device_id=self.id, file_path=path)
        if self.type is DeviceType.SIM:
            Simctl.get_screen(sim_id=self.id, file_path=path)
        if self.type is DeviceType.IOS:
            IDevice.get_screen(device_id=self.id, file_path=path)

        image_saved = False
        if File.exists(path):
            size = os.path.getsize(path)
            if size > 10:
                image_saved = True
        if image_saved:
            message = "Image of {0} saved at {1}".format(self.id, path)
            Log.log(level=log_level, msg=message)
        else:
            message = "Failed to save image of {0} saved at {1}".format(
                self.id, path)
            Log.error(message)
            raise Exception(message)
Example #2
0
    def test_06_copy_to_not_existing_file_with_restore(self):
        TestContext.BACKUP_FILES.clear()
        Folder.clean(Settings.BACKUP_FOLDER)
        # Path to files
        file_name = "app.android.add_style.scss"
        folder_name = os.path.join(self.current_folder, 'resources', 'new')
        Folder.clean(folder_name)
        Folder.create(folder_name)
        old_scss = os.path.join(self.current_folder, 'resources',
                                'app.android.scss')
        new_scss = os.path.join(folder_name, file_name)

        # Test Copy
        File.copy(source=old_scss, target=new_scss, backup_files=True)
        assert File.exists(new_scss)
        assert len(File.read(
            path=new_scss).splitlines()) == 14, 'Unexpected lines count.'
        assert not File.exists(os.path.join(Settings.BACKUP_FOLDER,
                                            file_name)), "File not backup!"
        assert TestContext.BACKUP_FILES.items()[0].__getitem__(
            0) == new_scss, "File path is not correct!"
        assert TestContext.BACKUP_FILES.items()[0].__getitem__(
            1) == file_name, "File name not correct!"

        # Revert
        TnsTest.restore_files()
        assert not File.exists(new_scss)
        assert not File.exists(os.path.join(Settings.BACKUP_FOLDER,
                                            file_name)), "File not deleted!"
        assert not TestContext.BACKUP_FILES, "File object not deleted!"
    def test_302_test_SBG_works_when_you_have_nativescript_property_in_package_json(self):
        """
         https://github.com/NativeScript/android-runtime/issues/1409
        """

        source_js = os.path.join(TEST_RUN_HOME, 'assets', 'runtime', 'android', 'files', 'android-runtime-1409',
                                 'package.json')
        target_js = os.path.join(TEST_RUN_HOME, APP_NAME, 'app', 'package.json')
        File.copy(source=source_js, target=target_js, backup_files=True)
        source_js = os.path.join(TEST_RUN_HOME, 'assets', 'runtime', 'android', 'files', 'android-runtime-1409',
                                 'new', 'package.json')
        target_js = os.path.join(TEST_RUN_HOME, APP_NAME, 'app', 'new', 'package.json')
        new_folder = os.path.join(TEST_RUN_HOME, APP_NAME, 'app', 'new')
        Folder.create(new_folder)
        File.copy(source=source_js, target=target_js, backup_files=True)
        webpack_config = os.path.join(TEST_RUN_HOME, APP_NAME, 'webpack.config.js')
        old_string = 'new nsWebpack.GenerateNativeScriptEntryPointsPlugin("bundle"),'
        new_string = 'new CopyWebpackPlugin(dataToCopy), new nsWebpack.GenerateNativeScriptEntryPointsPlugin("bundle"),'
        File.replace(path=webpack_config, old_string=old_string, new_string=new_string, backup_files=True)
        old_string = 'const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));'
        new_string = """    const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot));
        const fileName = "package.json"
        const dataInfo =  {
            from: `../app/new/${fileName}`,
            to: `${dist}/new/${fileName}`,
        }
        env.externals = [fileName];
        const dataToCopy = [dataInfo];"""
        File.replace(path=webpack_config, old_string=old_string, new_string=new_string, backup_files=False)
        log = Tns.build_android(os.path.join(TEST_RUN_HOME, APP_NAME), verify=False).output
        test_result = Wait.until(lambda: "Project successfully built." in log, timeout=300, period=5)
        assert test_result, 'App not build correct! Logs:' + log
    def test_454_support_Kotlin_with_jar_without_use_kotlin(self):
        """
        Support gradle.properties file for enable Kotlin
        https://github.com/NativeScript/android-runtime/issues/1459
        https://github.com/NativeScript/android-runtime/issues/1463
        """
        Tns.plugin_remove("sample-plugin-2", verify=False, path=APP_NAME)
        Adb.clear_logcat(self.emulator.id)
        source_app_gradle = os.path.join(TEST_RUN_HOME, 'assets', 'runtime', 'android', 'files',
                                         'android-runtime-1463-1459', 'test-jar-1.0-SNAPSHOT.jar')
        target_app_gradle = os.path.join(TEST_RUN_HOME, APP_NAME, 'app', 'App_Resources', 'Android', 'libs')
        Folder.create(target_app_gradle)
        File.copy(source=source_app_gradle, target=target_app_gradle, backup_files=True)
        source_js = os.path.join(TEST_RUN_HOME, 'assets', 'runtime', 'android', 'files', 'android-runtime-1463-1459',
                                 'main-view-model.js')
        target_js = os.path.join(TEST_RUN_HOME, APP_NAME, 'app', 'main-view-model.js')
        File.copy(source=source_js, target=target_js, backup_files=True)
        log = Tns.run_android(APP_NAME, device=self.emulator.id, wait=False, verify=False)

        strings = ['Project successfully built',
                   'Successfully installed on device with identifier', self.emulator.id,
                   'Successfully synced application']

        test_result = Wait.until(lambda: all(string in File.read(log.log_file) for string in strings), timeout=300,
                                 period=5)
        messages = "App with Kotlin enabled and kotlin jar not build correctly! Logs: "
        assert test_result, messages + File.read(log.log_file)
        self.assert_kotlin_is_working(self.emulator)
    def test_453_support_gradle_properties_for_enable_Kotlin_with_kotlin_file(self):
        """
        Support gradle.properties file for enable Kotlin
        https://github.com/NativeScript/android-runtime/issues/1459
        https://github.com/NativeScript/android-runtime/issues/1463
        """
        Tns.platform_remove(APP_NAME, platform=Platform.ANDROID)
        Tns.platform_add_android(APP_NAME, framework_path=Android.FRAMEWORK_PATH)
        Adb.clear_logcat(self.emulator.id)
        source_app_gradle = os.path.join(TEST_RUN_HOME, 'assets', 'runtime', 'android', 'files',
                                         'android-runtime-1463-1459', 'gradle.properties')
        target_app_gradle = os.path.join(TEST_RUN_HOME, APP_NAME, 'app', 'App_Resources', 'Android')
        File.copy(source=source_app_gradle, target=target_app_gradle, backup_files=True)
        source_app_gradle = os.path.join(TEST_RUN_HOME, 'assets', 'runtime', 'android', 'files',
                                         'android-runtime-1463-1459', 'Test.kt')
        target_app_gradle = os.path.join(TEST_RUN_HOME, APP_NAME, 'app', 'App_Resources', 'Android', 'src', 'main',
                                         'java', 'com')
        Folder.create(target_app_gradle)
        File.copy(source=source_app_gradle, target=target_app_gradle, backup_files=True)
        source_js = os.path.join(TEST_RUN_HOME, 'assets', 'runtime', 'android', 'files', 'android-runtime-1463-1459',
                                 'main-view-model.js')
        target_js = os.path.join(TEST_RUN_HOME, APP_NAME, 'app', 'main-view-model.js')
        File.copy(source=source_js, target=target_js, backup_files=True)
        log = Tns.run_android(APP_NAME, device=self.emulator.id, wait=False, verify=False)

        strings = ['Project successfully built',
                   'Successfully installed on device with identifier', self.emulator.id,
                   'Successfully synced application']

        test_result = Wait.until(lambda: all(string in File.read(log.log_file) for string in strings), timeout=300,
                                 period=5)
        messages = "App with Kotlin enabled and kotlin jar not build correctly! Logs: "
        assert test_result, messages + File.read(log.log_file)
        self.assert_kotlin_is_working(self.emulator)
Example #6
0
    def test_115_tns_run_android_add_remove_files_and_folders(self):
        """
        Add/delete files and folders should be synced properly
        """
        # Run app and verify on device
        result = run_hello_world_js_ts(self.app_name, Platform.ANDROID, self.emu)

        # Add new file
        # To verify that file is synced on device we have to refer some function
        # from it and verify it is executed. We will use console.log
        app_folder = os.path.join(self.source_project_dir, 'app')
        new_file = os.path.join(app_folder, 'test.js')
        renamed_file = os.path.join(app_folder, 'test_2.js')
        app_js_file = os.path.join(app_folder, 'app.js')
        File.write(new_file, "console.log('test.js synced!!!');")
        File.append(app_js_file, "require('./test.js');")
        strings = ["JS: test.js synced!!!"]
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)

        # Rename file

        os.rename(new_file, renamed_file)
        File.replace(renamed_file, 'test.js', 'renamed file')
        time.sleep(1)
        File.replace(app_js_file, 'test.js', 'test_2.js')
        strings = ["JS: renamed file synced!!!"]
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)

        # Delete file
        File.delete(renamed_file)
        strings = ["Module build failed", "Error: ENOENT: no such file or directory",
                   'Successfully synced application']
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)
        self.emu.wait_for_text(text='Exception')

        File.replace(app_js_file, "require('./test_2.js');", ' ')
        strings = TnsLogs.run_messages(app_name=self.app_name, platform=Platform.ANDROID,
                                       device=self.emu, run_type=RunType.UNKNOWN)
        not_existing_strings = ['12345']
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings,
                             not_existing_string_list=not_existing_strings)
        self.emu.wait_for_text(text=Changes.JSHelloWord.JS.old_text)

        # Add folder
        folder_name = os.path.join(app_folder, 'test_folder')
        new_file = os.path.join(folder_name, 'test_in_folder.js')
        Folder.create(folder_name)
        File.write(new_file, "console.log('test_in_folder.js synced!!!');")
        File.append(app_js_file, "require('./test_folder/test_in_folder.js');")
        strings = ["JS: test_in_folder.js synced!!!"]
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)

        # Delete folder
        Folder.clean(folder_name)
        strings = ["Module build failed", "Error: ENOENT: no such file or directory"]
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)
        self.emu.wait_for_text(text='Exception')
Example #7
0
 def test_01_file_download(self):
     file_name = "nativescript-logo.png"
     file_path_default = TEST_RUN_HOME
     file_path = os.path.join(TEST_RUN_HOME, "test")
     url = "https://www.nativescript.org/images/default-source/logos/nativescript-logo.png"
     File.download(file_name, url)
     Folder.create(file_path)
     assert File.exists(os.path.join(file_path_default, file_name))
     File.download(file_name, url, file_path)
     assert File.exists(os.path.join(file_path, file_name))
Example #8
0
    def test_115_tns_run_ios_add_remove_files_and_folders(self):
        """
        Add/delete files and folders should be synced properly
        """
        # Run app and verify on device
        result = run_hello_world_js_ts(self.app_name, Platform.IOS, self.sim)

        # Add new file
        # To verify that file is synced on device we have to refer some function
        # from it and verify it is executed. We will use console.log
        app_folder = os.path.join(self.source_project_dir, 'app')
        new_file = os.path.join(app_folder, 'test.js')
        renamed_file = os.path.join(app_folder, 'test_2.js')
        app_js_file = os.path.join(app_folder, 'main-view-model.js')
        File.write(new_file, "console.log('test.js synced!!!');")
        File.append(app_js_file, "require('./test.js');")
        strings = ["test.js synced!!!"]
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)

        # Rename file
        os.rename(new_file, renamed_file)
        File.replace(renamed_file, 'test.js', 'renamed file')
        time.sleep(1)
        File.replace(app_js_file, 'test.js', 'test_2.js')
        strings = ["renamed file synced!!!"]
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)

        # Delete file
        File.delete(renamed_file)
        strings = ["Module build failed: Error: ENOENT", "NativeScript debugger detached"]
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)

        File.replace(app_js_file, "require('./test_2.js');", ' ')
        strings = TnsLogs.run_messages(app_name=self.app_name, platform=Platform.IOS,
                                       device=self.sim, run_type=RunType.UNKNOWN)
        not_existing_strings = ['123']
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings,
                             not_existing_string_list=not_existing_strings)
        self.sim.wait_for_text(text=Changes.JSHelloWord.JS.old_text)

        # Add folder
        folder_name = os.path.join(app_folder, 'test_folder')
        new_file = os.path.join(folder_name, 'test_in_folder.js')
        Folder.create(folder_name)
        File.write(new_file, "console.log('test_in_folder.js synced!!!');")
        File.append(app_js_file, "require('./test_folder/test_in_folder.js');")
        strings = ["test_in_folder.js synced!!!"]
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)
        self.sim.wait_for_text(text=Changes.JSHelloWord.JS.old_text)

        # Delete folder
        Folder.clean(folder_name)
        strings = ["Module build failed: Error: ENOENT"]
        TnsLogs.wait_for_log(log_file=result.log_file, string_list=strings)
Example #9
0
    def setUpClass(cls):
        # Get class name and log
        TestContext.STARTED_PROCESSES = []
        TestContext.STARTED_DEVICES = []
        TestContext.TEST_APP_NAME = None
        TestContext.CLASS_NAME = cls.__name__
        try:
            for item in inspect.stack():
                TestContext.CLASS_NAME = item[0].f_locals['cls'].__name__
        except Exception:
            pass
        Log.test_class_start(class_name=TestContext.CLASS_NAME)

        # Kill processes
        Adb.restart()
        Tns.kill()
        Gradle.kill()
        TnsTest.kill_emulators()
        TnsTest.__clean_backup_folder_and_dictionary()
        # Ensure log folders are create
        Folder.create(Settings.TEST_OUT_HOME)
        Folder.create(Settings.TEST_OUT_LOGS)
        Folder.create(Settings.TEST_OUT_IMAGES)
        Folder.create(Settings.TEST_OUT_TEMP)

        # Set default simulator based on Xcode version
        if Settings.HOST_OS == OSType.OSX:
            if Xcode.get_version() < 10:
                Settings.Simulators.DEFAULT = Settings.Simulators.SIM_IOS11
            else:
                if Xcode.get_version() < 11:
                    Settings.Simulators.DEFAULT = Settings.Simulators.SIM_IOS12
                else:
                    Settings.Simulators.DEFAULT = Settings.Simulators.SIM_IOS13
Example #10
0
    def test_07_copy_to_existing_file_with_restore(self):
        TestContext.BACKUP_FILES.clear()
        Folder.clean(Settings.BACKUP_FOLDER)

        # Path to files
        file_name = "app.android.add_style.scss"
        folder_name = os.path.join(self.current_folder, 'resources', 'new')
        Folder.clean(folder_name)
        Folder.create(folder_name)
        old_scss = os.path.join(self.current_folder, 'resources',
                                'app.android.scss')
        new_scss = os.path.join(folder_name, file_name)
        old_value = 'Android here'
        new_value = 'Android here\n.page { background-color: red;}'

        # Create new file (so we don't break original one).
        File.copy(source=old_scss, target=new_scss)
        assert len(File.read(
            path=new_scss).splitlines()) == 14, 'Unexpected lines count.'

        # Replace
        File.replace(path=new_scss, old_string=old_value, new_string=new_value)
        content = File.read(path=new_scss)
        assert 'red;' in content, 'Failed to replace string.'
        assert len(content.splitlines()) == 15, 'Unexpected lines count.'

        # Test Copy
        File.copy(source=old_scss, target=new_scss, backup_files=True)
        content = File.read(path=new_scss)
        assert 'red;' not in content, 'Failed to copy file!'
        assert len(content.splitlines()) == 14, 'Unexpected lines count.'
        assert File.exists(new_scss)
        assert File.exists(os.path.join(Settings.BACKUP_FOLDER,
                                        file_name)), "File not backup!"
        assert TestContext.BACKUP_FILES.items()[0].__getitem__(
            0) == new_scss, "File path is not correct!"
        assert TestContext.BACKUP_FILES.items()[0].__getitem__(
            1) == file_name, "File name not correct!"

        # Revert
        TnsTest.restore_files()
        assert File.exists(new_scss)
        content = File.read(path=new_scss)
        assert 'red;' in content, 'Failed to replace string.'
        assert len(content.splitlines()) == 15, 'Unexpected lines count.'
        assert not File.exists(os.path.join(Settings.BACKUP_FOLDER,
                                            file_name)), "File not deleted!"
        assert not TestContext.BACKUP_FILES, "File object not deleted!"

        File.delete(path=new_scss)
    def test_301_build_project_with_space_release(self):

        # Ensure ANDROID_KEYSTORE_PATH contain spaces (verification for CLI issue 2650)
        Folder.create("with space")
        file_name = os.path.basename(Settings.Android.ANDROID_KEYSTORE_PATH)
        cert_with_space_path = os.path.join("with space", file_name)
        File.copy(Settings.Android.ANDROID_KEYSTORE_PATH, cert_with_space_path)

        Tns.build_android(app_name='"' + self.app_name_with_space + '"', release=True)
        output = File.read(os.path.join(self.app_name_with_space, "package.json"))
        assert self.app_identifier in output.lower()

        output = File.read(os.path.join(TnsPaths.get_platforms_android_src_main_path(self.app_name_with_space),
                                        'AndroidManifest.xml'))
        assert self.app_identifier in output.lower()
Example #12
0
    def get_screenshots():
        # get host snapshot
        base_path = os.path.join(Settings.TEST_OUT_IMAGES, TestContext.CLASS_NAME, TestContext.TEST_NAME)
        try:
            import pyautogui
            png_path = os.path.join(base_path, 'host.png')
            File.delete(png_path)
            Folder.create(folder=os.path.dirname(png_path))
            pyautogui.screenshot().save(png_path)
            Log.info("Saved host os screen at {0}".format(png_path))
        except Exception:
            Log.warning('Failed to take screenshot of host os.')

        # get device screenshots
        for device in TestContext.STARTED_DEVICES:
            try:
                png_path = os.path.join(base_path, device.name + '.png')
                File.delete(png_path)
                device.get_screen(png_path)
            except AssertionError:
                Log.warning('Failed to take screenshot of {0}'.format(device.id))
Example #13
0
 def test_442_assert_arm64_is_enabled_by_default(self):
     """
      Test arm64-v8 is enabled by default
     """
     Tns.build_android(os.path.join(TEST_RUN_HOME, APP_NAME), verify=True)
     apk_folder = os.path.join(TEST_RUN_HOME, APP_NAME, "platforms",
                               "android", "app", "build", "outputs", "apk",
                               "debug")
     apk_file = os.path.join(apk_folder, "app-debug.apk")
     apk_folder_to_unzip = os.path.join(apk_folder, "apk")
     Folder.create(apk_folder_to_unzip)
     command = "unzip " + apk_file + " -d " + apk_folder_to_unzip
     run(command, wait=False)
     time.sleep(20)
     unzip_apk_folder = os.path.join(apk_folder, "apk")
     arm64_folder = os.path.join(unzip_apk_folder, "lib", "arm64-v8a")
     assert Folder.exists(
         arm64_folder), "arm64-v8a architecture is missing!"
     error_message = "libNativeScript.so in arm64-v8a folder is missing!"
     assert File.exists(os.path.join(arm64_folder,
                                     "libNativeScript.so")), error_message
Example #14
0
 def setUpClass(cls):
     Folder.clean(Settings.TEST_OUT_HOME)
     Folder.create(Settings.TEST_OUT_LOGS)
     Folder.create(Settings.TEST_OUT_IMAGES)
     Folder.create(Settings.TEST_OUT_TEMP)
     Process.kill(proc_name="adb")
     TnsRunAndroidTest.setUpClass()
     url = "https://github.com/webdriverio/native-demo-app/releases/download/0.2.1/Android-NativeDemoApp-0.2.1.apk"
     cls.apk_path = os.path.join(Settings.TEST_RUN_HOME, "test.apk")
     File.delete(path=cls.apk_path)
     File.download(file_name='test.apk', url=url, destination_dir=Settings.TEST_RUN_HOME)
Example #15
0
def __cleanup():
    """
    Wipe TEST_OUT_HOME.
    """
    Folder.clean(os.path.join(Settings.TEST_RUN_HOME, 'node_modules'))
    Folder.clean(Settings.TEST_OUT_HOME)
    Folder.create(Settings.TEST_OUT_LOGS)
    Folder.create(Settings.TEST_OUT_IMAGES)
    Folder.create(Settings.TEST_OUT_TEMP)

    DeviceManager.Emulator.stop()
    if Settings.HOST_OS == OSType.OSX:
        DeviceManager.Simulator.stop()

    Adb.restart()
    Tns.kill()
    Gradle.kill()
Example #16
0
def run(cmd,
        cwd=Settings.TEST_RUN_HOME,
        wait=True,
        timeout=600,
        fail_safe=False,
        register=True,
        log_level=logging.DEBUG):
    # Init result values
    time_string = datetime.now().strftime('%Y_%m_%d_%H_%M_%S_%f')
    log_file = os.path.join(Settings.TEST_OUT_LOGS,
                            'command_{0}.txt'.format(time_string))
    complete = False
    duration = None
    output = ''

    # Ensure logs folder exists
    dir_path = os.path.dirname(os.path.realpath(log_file))
    Folder.create(dir_path)

    # Command settings
    if not wait:
        # Redirect output to file
        File.write(path=log_file, text=cmd + os.linesep + '====>' + os.linesep)
        cmd = cmd + ' >> ' + log_file + ' 2>&1 &'

    # Log command that will be executed:
    Log.log(level=log_level, msg='Execute command: ' + cmd)
    Log.log(level=logging.DEBUG, msg='CWD: ' + cwd)

    # Execute command:
    if wait:
        start = time.time()
        with open(log_file, mode='w') as log:
            if Settings.HOST_OS == OSType.WINDOWS:
                process = subprocess.Popen(cmd,
                                           cwd=cwd,
                                           shell=True,
                                           stdout=log,
                                           stderr=log)
            else:
                process = subprocess.Popen(cmd,
                                           cwd=cwd,
                                           shell=True,
                                           stdout=subprocess.PIPE,
                                           stderr=log)

        # Wait until command complete
        try:
            process.wait(timeout=timeout)
            complete = True
            out, err = process.communicate()
            if out is not None:
                if Settings.PYTHON_VERSION < 3:
                    output = str(out.decode('utf8').encode('utf8')).strip()
                else:
                    output = out.decode("utf-8").strip()
        except subprocess.TimeoutExpired:
            process.kill()
            if fail_safe:
                Log.error('Command "{0}" timeout after {1} seconds.'.format(
                    cmd, timeout))
            else:
                raise

        # Append stderr to output
        stderr = File.read(path=log_file)
        if stderr:
            output = output + os.linesep + File.read(path=log_file)

        # noinspection PyBroadException
        try:
            File.delete(path=log_file)
        except Exception:
            Log.debug('Failed to clean log file: {0}'.format(log_file))
        log_file = None
        end = time.time()
        duration = end - start
    else:
        process = psutil.Popen(cmd,
                               cwd=cwd,
                               shell=True,
                               stdin=None,
                               stdout=None,
                               stderr=None,
                               close_fds=True)

    # Get result
    pid = process.pid
    exit_code = process.returncode

    # Log output of the process
    if wait:
        Log.log(level=log_level,
                msg='OUTPUT: ' + os.linesep + output + os.linesep)
    else:
        Log.log(level=log_level,
                msg='OUTPUT REDIRECTED: ' + log_file + os.linesep)

    # Construct result
    result = ProcessInfo(cmd=cmd,
                         pid=pid,
                         exit_code=exit_code,
                         output=output,
                         log_file=log_file,
                         complete=complete,
                         duration=duration)

    # Register in TestContext
    if psutil.pid_exists(result.pid) and register:
        TestContext.STARTED_PROCESSES.append(result)

    # Return the result
    return result