diff --git a/ci/Jenkinsfile.tests-e2e.windows b/ci/Jenkinsfile.tests-e2e.windows new file mode 100644 index 00000000000..2a3fb4ad8a7 --- /dev/null +++ b/ci/Jenkinsfile.tests-e2e.windows @@ -0,0 +1,287 @@ +#!/usr/bin/env groovy +library 'status-jenkins-lib@v1.9.28' + +pipeline { + + agent { + label 'windows && x86_64 && qt-6.9.2 && windows-e2e' + } + + parameters { + gitParameter( + name: 'GIT_REF', + description: 'Git branch to checkout.', + branchFilter: 'origin/(.*)', + branch: '', + defaultValue: 'master', + quickFilterEnabled: false, + selectedValue: 'DEFAULT', + sortMode: 'ASCENDING_SMART', + tagFilter: '*', + type: 'PT_BRANCH' + ) + string( + name: 'BUILD_SOURCE', + description: 'URL to tar.gz file OR path to Jenkins build.', + defaultValue: getDefaultBuildSource() + ) + string( + name: 'TEST_NAME', + description: 'Paste test name/part of test name to run specific test.', + defaultValue: '' + ) + string( + name: 'TEST_SCOPE_FLAG', + description: 'Paste a known mark to run tests labeled with this mark', + defaultValue: getDefaultTestScopeFlag() + ) + string( + name: 'TESTRAIL_RUN_NAME', + description: 'Test run name in Test Rail.', + defaultValue: '' + ) + choice( + name: 'LOG_LEVEL', + description: 'Log level for pytest.', + choices: ['INFO', 'DEBUG', 'TRACE', 'WARNING', 'CRITICAL'] + ) + } + + options { + timestamps() + /* Prevent Jenkins jobs from running forever */ + timeout(time: 120, unit: 'MINUTES') + /* manage how many builds we keep */ + buildDiscarder(logRotator( + daysToKeepStr: '60', + numToKeepStr: '50', + artifactNumToKeepStr: '50', + )) + disableRestartFromStage() + } + + environment { + PLATFORM = 'tests/e2e' + + SQUISH_DIR = 'C:\\squish-runner-9.0.1-qt-6.9' + PYTHONPATH = "${SQUISH_DIR}\\lib;${SQUISH_DIR}\\bin;${SQUISH_DIR}\\lib\\python;${PYTHONPATH}" + + /* To stop e2e tests using port 8545 */ + STATUS_RUNTIME_HTTP_API = 'False' + STATUS_RUNTIME_WS_API = 'False' + + /* Avoid race conditions with other builds using virtualenv. */ + VIRTUAL_ENV = "${WORKSPACE_TMP}\\venv" + PATH = "${VIRTUAL_ENV}\\bin;C:\\Qt\\6.9.2\\msvc2022_64\\bin;${PATH}" + + /* To store user configuratiin files in temp dir */ + XDG_CONFIG_HOME = "${WORKSPACE_TMP}/config" + + TESTRAIL_URL = 'https://ethstatus.testrail.net' + TESTRAIL_PROJECT_ID = 18 + /* Override QT xcb plugin with linux to avoid errors like: + * "Could not load the Qt platform plugin "xcb" in "" even though it was found." + QT_QPA_PLATFORM = "linuxfb"*/ + + /* Runtime flag to make testing of the app easier. Switched off: unpredictable app behavior under new tests */ + STATUS_RUNTIME_TEST_MODE = 1 + + /* Logging rules let you enable or disable logging for categories */ + /* QT_LOGGING_RULES = '*.warning=true' */ + + /* Set to a non-zero value to make Qt print out diagnostic information about the each (C++) plugin it tries to load. */ + /* QT_DEBUG_PLUGINS = 0 */ + + /* Hack fix for params not being set in job on first run */ + BUILD_SOURCE = "${params.BUILD_SOURCE}" + TEST_NAME = "${params.TEST_NAME}" + TEST_SCOPE_FLAG = "${params.TEST_SCOPE_FLAG}" + TESTRAIL_RUN_NAME = "${params.TESTRAIL_RUN_NAME}" + LOG_LEVEL = "${params.LOG_LEVEL}" + + /* Forces QT to use OpenGL for rendering instead of default Direct3D 11 */ + QSG_RHI_BACKEND = 'opengl' + } + + stages { + stage('Cleanup Workspace') { + steps { + sh './scripts/clean-git.sh' + } + } + stage('Prep') { + steps { script { + setNewBuildName() + updateGitHubStatus() + } } + } + + stage('Deps') { + steps { script { dir('test/e2e') { + bat """ + python310 -m venv ${VIRTUAL_ENV} + python310 -m pip install --upgrade pip + python310 -m pip install -r requirements.txt + """ + } } } + } + + stage('Download') { + when { expression { params.BUILD_SOURCE.startsWith('http') } } + steps { timeout(5) { script { dir('test/e2e') { + sh 'mkdir -p ./pkg/' + setBuildDescFromFile(params.BUILD_SOURCE) + fileOperations([ + fileDownloadOperation( + url: params.BUILD_SOURCE, + targetFileName: 'StatusIm-Desktop.7z', + targetLocation: './pkg/', + userName: '', + password: '', + ) + ]) + } } } } + } + + stage('Copy') { + when { expression { ! params.BUILD_SOURCE.startsWith('http') } } + steps { timeout(5) { script { dir('test/e2e') { + copyArtifacts( + projectName: params.BUILD_SOURCE, + filter: 'pkg/*-x86_64.7z', + selector: lastWithArtifacts(), + target: './' + ) + setBuildDescFromFile(utils.findFile('pkg/*7z')) + } } } } + } + + stage('Unpack') { + steps { timeout(5) { script { dir('test/e2e') { + sh 'mkdir aut' + sh "7z x '${utils.findFile('pkg/*.7z')}' -o'./aut'" + env.AUT_PATH = utils.findFile('aut/Status/bin/Status.exe').replace('\\','/') + } } } } + } + + stage('Test') { + steps { + timeout(time: getTestStageTimeout()) { + dir('test/e2e') { + /* Lock the agent so we run only one e2e build at a time */ + lock(resource: "e2e-windows-${env.NODE_NAME}", quantity: 1) { + script { + def flags = [] + if (params.TEST_NAME) { flags.add("-k=${params.TEST_NAME}") } + if (params.TEST_SCOPE_FLAG) { flags.add(params.TEST_SCOPE_FLAG) } + if (params.LOG_LEVEL) { flags.addAll(["--log-level=${params.LOG_LEVEL}", "--log-cli-level=${params.LOG_LEVEL}"]) } + def flagStr = flags.join(' ') + + withCredentials([ + usernamePassword(credentialsId: 'test-rail-api-devops', usernameVariable: 'TESTRAIL_USR', passwordVariable: 'TESTRAIL_PSW'), + string(credentialsId: 'wallet-test-user-seed', variable: 'WALLET_TEST_USER_SEED') + ]) { + sh""" + pushd configs + ln -sf _local.ci.py _local.py || cp _local.ci.py _local.py + popd + + """ + bat""" + python310 -m pytest -m "not keycard" -v --reruns=1 --timeout=300 ${flagStr} --disable-warnings --alluredir=./allure-results -o timeout_func_only=true + """ + } + } + } + } + } + } + } + } + + post { + always { script { dir('test/e2e') { + archiveArtifacts('aut/Status/bin/*.log') + + /* Needed to categorize types of errors and add environment section in allure report. */ + sh 'cp ext/allure_files/categories.json allure-results' + sh 'cp ext/allure_files/environment.properties allure-results' + + allure([ + results: [[path: 'allure-results']], + reportBuildPolicy: 'ALWAYS', + properties: [], + jdk: '', + ]) + /* Link for Jenkins Builds GitHub comment. */ + env.PKG_URL = "${env.BUILD_URL}allure/" + updateGitHubStatus() + } } } + success { script { + github.notifyPR(true) + } } + failure { script { + github.notifyPR(false) + discord.send( + header: '**Desktop E2E test failure!**', + cred: 'discord-status-desktop-e2e-webhook', + ) + } } + cleanup { cleanWs(disableDeferredWipeout: true) } + } +} + +def setNewBuildName() { + if (currentBuild.upstreamBuilds) { + def parent = utils.parentOrCurrentBuild() + currentBuild.displayName = parent.getFullDisplayName().minus('status-desktop ยป ') + } +} + +def setBuildDescFromFile(fileNameOrPath) { + def tokens = utils.parseFilename(utils.baseName(fileNameOrPath)) + if (tokens == null) { /* Fallback for regex fail. */ + currentBuild.description = utils.baseName(fileNameOrPath) + return + } + if (tokens.build && tokens.build.startsWith('pr')) { + currentBuild.displayName = tokens.build.replace(/^pr/, 'PR-') + } + currentBuild.description = formatMap([ + Node: NODE_NAME, + Build: tokens.build, + Commit: tokens.commit, + Version: (tokens.tstamp ?: tokens.version), + ]) +} + +def updateGitHubStatus() { + /* For PR builds update check status. */ + if (params.BUILD_SOURCE ==~ /.*\/PR-[0-9]+\/?$/) { + github.statusUpdate( + context: 'jenkins/prs/tests/e2e-new.windows', + commit: jenkins.getJobCommitByPath(params.BUILD_SOURCE), + repo_url: 'https://github.com/status-im/status-desktop' + ) + } +} + +def formatMap(Map data=[:]) { + def text = '' + data.each { key, val -> text += "${key}: ${val}
\n" } + return text +} + +def getDefaultBuildSource() { + return '' +} + +def getDefaultTestScopeFlag() { + if (JOB_NAME == "status-desktop/systems/windows/x86_64/tests-e2e") { + return '' + } else { + return '-m=critical' + } +} + +def getTestStageTimeout() { params.TEST_SCOPE_FLAG == '-m=critical' ? 30 : 120 } diff --git a/ci/Jenkinsfile.windows b/ci/Jenkinsfile.windows index 8e770cc2050..50b0c3959e3 100644 --- a/ci/Jenkinsfile.windows +++ b/ci/Jenkinsfile.windows @@ -73,7 +73,7 @@ pipeline { QTDIR = "/c/Qt/6.9.2/msvc2022_64" PATH = "${env.QTDIR}/bin:${goPath()}/bin:${env.PATH}" /* Avoid weird bugs caused by stale cache. */ - QML_DISABLE_DISK_CACHE = "true" + QML_DISABLE_DISK_CACHE = 1 /* Control output the filename */ VERSION = sh(script: "./scripts/version.sh", returnStdout: true).trim() STATUS_CLIENT_EXE = "pkg/${utils.pkgFilename(ext: 'exe', arch: getArch(), version: env.VERSION)}" @@ -149,6 +149,21 @@ pipeline { jenkins.setBuildDesc(Zip: zip_url, Exe: exe_url) } } } + + + stage('E2E') { + when { expression { utils.isPRBuild() } } + steps { script { + build( + job: 'status-desktop/e2e/prs-windows', + wait: false, + parameters: jenkins.mapToParams([ + GIT_REF: env.GIT_COMMIT, + BUILD_SOURCE: env.JOB_NAME, + ]), + ) + } } + } } post { success { script { github.notifyPR(true) } } diff --git a/test/e2e/configs/__init__.py b/test/e2e/configs/__init__.py index d1257cf2f5c..e97569a7ea2 100644 --- a/test/e2e/configs/__init__.py +++ b/test/e2e/configs/__init__.py @@ -19,8 +19,8 @@ if AUT_PATH is None: exit('Please add "AUT_PATH" in ./configs/_local.py') -if get_platform() == "Windows" and 'bin' not in AUT_PATH: - exit('Please use launcher from "bin" folder in "AUT_PATH"') +if get_platform() == "Windows" and 'Status' not in AUT_PATH: + exit('Please use launcher from "Status" folder in "AUT_PATH"') AUT_PATH = SystemPath(AUT_PATH) WALLET_SEED = os.getenv('WALLET_TEST_USER_SEED') diff --git a/test/e2e/conftest.py b/test/e2e/conftest.py index a4fc17f85c6..2d627e0cf8a 100644 --- a/test/e2e/conftest.py +++ b/test/e2e/conftest.py @@ -4,6 +4,7 @@ import allure import pytest import shortuuid +import sys from tests import test_data from PIL import ImageGrab @@ -12,6 +13,9 @@ from scripts.utils.system_path import SystemPath # Send logs to pytest.log as well +# Ensure log directory exists +log_dir = os.path.dirname(configs.PYTEST_LOG) +os.makedirs(log_dir, exist_ok=True) handler = logging.FileHandler(filename=configs.PYTEST_LOG) logging.basicConfig( level=os.getenv('LOG_LEVEL', 'INFO'), @@ -28,9 +32,31 @@ ] +@pytest.fixture(scope='session', autouse=True) +def generate_allure_environment(): + """Generate allure environment.properties with dynamic platform information""" + env_dir = configs.testpath.ROOT / 'ext' / 'allure_files' + env_file = env_dir / 'environment.properties' + + # Ensure directory exists + env_dir.mkdir(parents=True, exist_ok=True) + + platform_name = get_platform() + python_version = f"Python {sys.version_info.major}.{sys.version_info.minor}" + + content = f"""os_platform = {platform_name} +python_version = {python_version} +""" + + env_file.write_text(content) + LOG.info(f'Generated allure environment.properties with platform={platform_name}, python={python_version}') + yield + + @pytest.fixture(scope='session', autouse=True) def setup_session_scope( # init_testrail_api, TODO: https://github.com/status-im/status-desktop/issues/18288 + generate_allure_environment, prepare_test_directory, start_squish_server ): diff --git a/test/e2e/tests/crtitical_tests_prs/test_create_edit_join_community_pin_unpin_message.py b/test/e2e/tests/crtitical_tests_prs/test_create_edit_join_community_pin_unpin_message.py index c376e5f02ec..6dc0ef1b3b3 100644 --- a/test/e2e/tests/crtitical_tests_prs/test_create_edit_join_community_pin_unpin_message.py +++ b/test/e2e/tests/crtitical_tests_prs/test_create_edit_join_community_pin_unpin_message.py @@ -3,6 +3,7 @@ from allure_commons._allure import step import driver +from configs import get_platform from constants.community import Channel from gui.main_window import MainWindow from helpers.multiple_instances_helper import ( @@ -26,6 +27,7 @@ @pytest.mark.communities @pytest.mark.smoke @pytest.mark.critical +@pytest.mark.skipif(get_platform() == 'Windows', reason="https://github.com/status-im/status-desktop/issues/18994") def test_create_edit_join_community_pin_unpin_message(multiple_instances): user_one: UserAccount = RandomUser() user_two: UserAccount = RandomUser() diff --git a/test/e2e/tests/crtitical_tests_prs/test_messaging_1x1_chat.py b/test/e2e/tests/crtitical_tests_prs/test_messaging_1x1_chat.py index 8d48c871118..bb28bc877f1 100644 --- a/test/e2e/tests/crtitical_tests_prs/test_messaging_1x1_chat.py +++ b/test/e2e/tests/crtitical_tests_prs/test_messaging_1x1_chat.py @@ -7,6 +7,9 @@ from allure_commons._allure import step import driver +from configs import get_platform +from constants.images_paths import HEART_EMOJI_PATH, ANGRY_EMOJI_PATH, THUMBSUP_EMOJI_PATH, THUMBSDOWN_EMOJI_PATH, \ + LAUGHING_EMOJI_PATH, SAD_EMOJI_PATH from constants.messaging import Messaging from constants.wallet import WalletAddress from ext.test_files.base64_images import BASE_64_IMAGE_JPEG @@ -25,6 +28,7 @@ @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703087', '1-1 Chat') @pytest.mark.case(703087, 738732, 738734, 738742, 738744, 738745) @pytest.mark.critical +@pytest.mark.skipif(get_platform() == 'Windows', reason="https://github.com/status-im/status-desktop/issues/18994") @pytest.mark.smoke def test_1x1_chat_add_contact_in_settings(multiple_instances): user_one: UserAccount = RandomUser() diff --git a/test/e2e/tests/crtitical_tests_prs/test_messaging_group_chat.py b/test/e2e/tests/crtitical_tests_prs/test_messaging_group_chat.py index dc412d4a8d9..640677aef32 100644 --- a/test/e2e/tests/crtitical_tests_prs/test_messaging_group_chat.py +++ b/test/e2e/tests/crtitical_tests_prs/test_messaging_group_chat.py @@ -6,6 +6,7 @@ from allure_commons._allure import step import driver +from configs import get_platform from constants.links import external_link, link_to_status_community import configs.testpath @@ -23,6 +24,7 @@ @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703014', 'Create a group and send messages') @pytest.mark.case(703014, 738735, 738736, 738739, 738740) @pytest.mark.critical +@pytest.mark.skipif(get_platform() == 'Windows', reason="https://github.com/status-im/status-desktop/issues/18994") @pytest.mark.smoke @pytest.mark.parametrize('community_name, domain_link, domain_link_2', [pytest.param('Status', 'status.app', 'github.com') diff --git a/test/e2e/tests/crtitical_tests_prs/test_onboarding_sync_with_code.py b/test/e2e/tests/crtitical_tests_prs/test_onboarding_sync_with_code.py index abfee556c5b..cd40725f900 100644 --- a/test/e2e/tests/crtitical_tests_prs/test_onboarding_sync_with_code.py +++ b/test/e2e/tests/crtitical_tests_prs/test_onboarding_sync_with_code.py @@ -7,6 +7,7 @@ import configs.testpath import driver +from configs import get_platform from configs.timeouts import APP_LOAD_TIMEOUT_MSEC from constants import UserAccount, RandomUser from constants.syncing import SyncingSettings @@ -19,6 +20,7 @@ @allure.testcase('https://ethstatus.testrail.net/index.php?/cases/view/703592', 'Sync device during onboarding') @pytest.mark.case(703592, 738760) @pytest.mark.critical +@pytest.mark.skipif(get_platform() == 'Windows', reason="https://github.com/status-im/status-desktop/issues/18846 on Windows") @pytest.mark.smoke def test_sync_devices_during_onboarding_change_settings_unpair(multiple_instances): user: UserAccount = RandomUser()