From 5d1c31fb7ef457a59c3de3800afc3e598083be0d Mon Sep 17 00:00:00 2001 From: toki Date: Sat, 30 May 2026 12:07:29 +0900 Subject: [PATCH] =?UTF-8?q?test(mattermost):=20Detox=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=ED=94=8C=EB=A1=9C=EC=9A=B0=EB=A5=BC=20=EC=95=88?= =?UTF-8?q?=EC=A0=95=ED=99=94=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detox/e2e/support/ui/component/alert.ts | 2 + .../detox/e2e/support/ui/screen/server.ts | 95 ++++++++++++++----- .../e2e/support/ui/screen/server_list.ts | 8 +- .../channels/server_login/server_list.e2e.ts | 8 +- .../channels/smoke_test/server_login.e2e.ts | 2 +- apps/mattermost/detox/e2e/test/setup.ts | 25 +++++ 6 files changed, 109 insertions(+), 31 deletions(-) diff --git a/apps/mattermost/detox/e2e/support/ui/component/alert.ts b/apps/mattermost/detox/e2e/support/ui/component/alert.ts index a96d41e5..8ca5ce6c 100644 --- a/apps/mattermost/detox/e2e/support/ui/component/alert.ts +++ b/apps/mattermost/detox/e2e/support/ui/component/alert.ts @@ -29,6 +29,7 @@ class Alert { markAllAsReadTitle = isAndroid() ? element(by.text('Are you sure you want to mark all threads as read?')) : element(by.label('Are you sure you want to mark all threads as read?')).atIndex(0); messageLengthTitle = isAndroid() ? element(by.text('Message Length')) : element(by.label('Message Length')).atIndex(0); notificationsCannotBeReceivedTitle = isAndroid() ? element(by.text('Notifications cannot be received from this server')) : element(by.label('Notifications cannot be received from this server')).atIndex(0); + notificationsCouldNotBeReceivedTitle = isAndroid() ? element(by.text('Notifications could not be received from this server')) : element(by.label('Notifications could not be received from this server')).atIndex(0); removeServerTitle = (serverDisplayName: string) => { const title = `Are you sure you want to remove ${serverDisplayName}?`; @@ -52,6 +53,7 @@ class Alert { noButton2 = isAndroid() ? element(by.text('NO')) : element(by.label('No')).atIndex(1); okButton = isAndroid() ? element(by.text('OK')) : element(by.label('OK')).atIndex(1); okayButton = isAndroid() ? element(by.text('Okay')) : element(by.label('Okay')).atIndex(1); + okayButtonUppercase = isAndroid() ? element(by.text('OKAY')) : element(by.label('Okay')).atIndex(1); removeButton = isAndroid() ? element(by.text('REMOVE')) : element(by.label('Remove')).atIndex(0); removeButton1 = isAndroid() ? element(by.text('REMOVE')) : element(by.label('Remove')).atIndex(1); removeButton2 = isAndroid() ? element(by.text('REMOVE')) : element(by.label('Remove')).atIndex(2); diff --git a/apps/mattermost/detox/e2e/support/ui/screen/server.ts b/apps/mattermost/detox/e2e/support/ui/screen/server.ts index 59867922..0038c993 100644 --- a/apps/mattermost/detox/e2e/support/ui/screen/server.ts +++ b/apps/mattermost/detox/e2e/support/ui/screen/server.ts @@ -52,6 +52,73 @@ class ServerScreen { return this.serverScreen; }; + tapOkayAlertButton = async () => { + try { + await Alert.okayButton.tap(); + } catch (error) { + if (isAndroid()) { + await Alert.okayButtonUppercase.tap(); + return; + } + + throw error; + } + }; + + dismissPushProxyAlertIfPresent = async () => { + try { + await waitFor(Alert.notificationsCannotBeReceivedTitle).toExist().withTimeout(timeouts.HALF_SEC); + await this.tapOkayAlertButton(); + await wait(timeouts.ONE_SEC); + return true; + } catch { + // Push proxy alert was not shown. + } + + try { + await waitFor(Alert.notificationsCouldNotBeReceivedTitle).toExist().withTimeout(timeouts.HALF_SEC); + await this.tapOkayAlertButton(); + await wait(timeouts.ONE_SEC); + return true; + } catch { + // Push proxy alert was not shown. + } + + return false; + }; + + dismissIosConnectionAlertIfNeeded = async (serverUrl: string) => { + if (!isIos() || (!serverUrl.includes('127.0.0.1') && process.env.CI)) { + return; + } + + try { + await waitFor(Alert.okayButton).toExist().withTimeout(timeouts.TEN_SEC); + await this.tapOkayAlertButton(); + } catch { + /* eslint-disable no-console */ + console.log('Alert button did not appear!'); + } + }; + + waitForLoginScreenAfterConnect = async () => { + const timeout = isAndroid() ? timeouts.ONE_MIN : timeouts.HALF_MIN; + const startTime = Date.now(); + + /* eslint-disable no-await-in-loop */ + while (Date.now() - startTime < timeout) { + try { + await waitFor(this.usernameInput).toBeVisible().withTimeout(timeouts.ONE_SEC); + return; + } catch { + await this.dismissPushProxyAlertIfPresent(); + } + } + /* eslint-enable no-await-in-loop */ + + await waitForElementToBeVisible(this.usernameInput, timeouts.ONE_SEC, timeouts.ONE_SEC); + }; + connectToServer = async (serverUrl: string, serverDisplayName: string) => { await this.toBeVisible(); await this.serverUrlInput.replaceText(serverUrl); @@ -61,21 +128,11 @@ class ServerScreen { } if (isIos()) { await this.tapConnectButton(); - if (serverUrl.includes('127.0.0.1') || !process.env.CI) { - try { - // # Tap alert okay button - await waitFor(Alert.okayButton).toExist().withTimeout(timeouts.TEN_SEC); - await Alert.okayButton.tap(); - } catch (error) { - /* eslint-disable no-console */ - console.log('Alert button did not appear!'); - } - } + await this.dismissIosConnectionAlertIfNeeded(serverUrl); } // The bridge can be busy during login transition, use waitFor without idle check - const timeout = isAndroid() ? timeouts.ONE_MIN : timeouts.HALF_MIN; - await waitForElementToBeVisible(this.usernameInput, timeout, timeouts.ONE_SEC); + await this.waitForLoginScreenAfterConnect(); }; close = async () => { @@ -115,21 +172,11 @@ class ServerScreen { } if (isIos()) { await this.tapConnectButton(); - if (serverUrl.includes('127.0.0.1') || !process.env.CI) { - try { - // # Tap alert okay button - await waitFor(Alert.okayButton).toExist().withTimeout(timeouts.TEN_SEC); - await Alert.okayButton.tap(); - } catch (error) { - /* eslint-disable no-console */ - console.log('Alert button did not appear!'); - } - } + await this.dismissIosConnectionAlertIfNeeded(serverUrl); } // The bridge can be busy during login transition, so poll for the element without waiting for idle - const timeout = isAndroid() ? timeouts.ONE_MIN : timeouts.HALF_MIN; - await waitForElementToBeVisible(this.usernameInput, timeout, timeouts.ONE_SEC); + await this.waitForLoginScreenAfterConnect(); }; } diff --git a/apps/mattermost/detox/e2e/support/ui/screen/server_list.ts b/apps/mattermost/detox/e2e/support/ui/screen/server_list.ts index 120c0bc8..1ddca85e 100644 --- a/apps/mattermost/detox/e2e/support/ui/screen/server_list.ts +++ b/apps/mattermost/detox/e2e/support/ui/screen/server_list.ts @@ -9,14 +9,14 @@ class ServerListScreen { testID = { serverListScreen: 'server_list.screen', serverListTitle: 'server_list.title', - addServerButton: 'server_list.add_a_server.button', + addServerButton: 'servers.create_button', tutorialHighlight: 'tutorial_highlight', tutorialSwipeLeft: 'tutorial_swipe_left', }; serverListScreen = element(by.id(this.testID.serverListScreen)); serverListTitle = element(by.id(this.testID.serverListTitle)); - addServerButton = element(by.text('Add a server')); + addServerButton = element(by.id(this.testID.addServerButton)); tutorialHighlight = element(by.id(this.testID.tutorialHighlight)); tutorialSwipeLeft = element(by.id(this.testID.tutorialSwipeLeft)); @@ -67,6 +67,10 @@ class ServerListScreen { return this.toBeVisible(); }; + tapAddServerButton = async () => { + await this.addServerButton.tap(); + }; + close = async () => { if (isIos()) { await this.serverListScreen.swipe('down'); diff --git a/apps/mattermost/detox/e2e/test/products/channels/server_login/server_list.e2e.ts b/apps/mattermost/detox/e2e/test/products/channels/server_login/server_list.e2e.ts index 4ed4d83f..37e35bbd 100644 --- a/apps/mattermost/detox/e2e/test/products/channels/server_login/server_list.e2e.ts +++ b/apps/mattermost/detox/e2e/test/products/channels/server_login/server_list.e2e.ts @@ -88,7 +88,7 @@ describe('Server Login - Server List', () => { await User.apiAdminLogin(siteTwoUrl); ({user: serverTwoUser} = await Setup.apiInit(siteTwoUrl)); await wait(timeouts.TWO_SEC); - await ServerListScreen.addServerButton.tap(); + await ServerListScreen.tapAddServerButton(); await expect(ServerScreen.headerTitleAddServer).toBeVisible(35); await ServerScreen.connectToServer(serverTwoUrl, serverTwoDisplayName); await LoginScreen.login(serverTwoUser); @@ -114,7 +114,7 @@ describe('Server Login - Server List', () => { await User.apiAdminLogin(siteThreeUrl); ({user: serverThreeUser} = await Setup.apiInit(siteThreeUrl)); await wait(timeouts.TWO_SEC); - await ServerListScreen.addServerButton.tap(); + await ServerListScreen.tapAddServerButton(); await expect(ServerScreen.headerTitleAddServer).toBeVisible(35); await ServerScreen.connectToServer(serverThreeUrl, serverThreeDisplayName); await LoginScreen.login(serverThreeUser); @@ -251,7 +251,7 @@ describe('Server Login - Server List', () => { await expect(ServerListScreen.getServerItemInactive(serverOneDisplayName)).not.toExist(); // # Add first server back to the list and log in to the first server - await ServerListScreen.addServerButton.tap(); + await ServerListScreen.tapAddServerButton(); await expect(ServerScreen.headerTitleAddServer).toBeVisible(35); await ServerScreen.connectToServer(serverOneUrl, serverOneDisplayName); await LoginScreen.login(serverOneUser); @@ -301,7 +301,7 @@ describe('Server Login - Server List', () => { await waitFor(ServerListScreen.serverListTitle).toBeVisible().withTimeout(timeouts.TWO_SEC); await ServerListScreen.serverListTitle.swipe('up', 'fast', 0.1, 0.5, 0.3); } - await ServerListScreen.addServerButton.tap(); + await ServerListScreen.tapAddServerButton(); await expect(ServerScreen.headerTitleAddServer).toBeVisible(35); await ServerScreen.serverUrlInput.replaceText(serverTwoUrl); if (isAndroid()) { diff --git a/apps/mattermost/detox/e2e/test/products/channels/smoke_test/server_login.e2e.ts b/apps/mattermost/detox/e2e/test/products/channels/smoke_test/server_login.e2e.ts index a809c09f..15a25c1c 100644 --- a/apps/mattermost/detox/e2e/test/products/channels/smoke_test/server_login.e2e.ts +++ b/apps/mattermost/detox/e2e/test/products/channels/smoke_test/server_login.e2e.ts @@ -68,7 +68,7 @@ describe('Smoke Test - Server Login', () => { // # Add a second server and log in to the second server await User.apiAdminLogin(siteTwoUrl); const {user} = await Setup.apiInit(siteTwoUrl); - await ServerListScreen.addServerButton.tap(); + await ServerListScreen.tapAddServerButton(); await wait(timeouts.ONE_SEC); await waitFor(ServerScreen.headerTitleAddServer).toBeVisible().withTimeout(timeouts.TEN_SEC); await ServerScreen.connectToServer(serverTwoUrl, serverTwoDisplayName); diff --git a/apps/mattermost/detox/e2e/test/setup.ts b/apps/mattermost/detox/e2e/test/setup.ts index 6459a0fc..41a19464 100644 --- a/apps/mattermost/detox/e2e/test/setup.ts +++ b/apps/mattermost/detox/e2e/test/setup.ts @@ -2,10 +2,15 @@ // See LICENSE.txt for license information. /* eslint-disable no-await-in-loop, no-console */ +import {execFile} from 'child_process'; +import {promisify} from 'util'; + import {ClaudePromptHandler} from '@support/pilot/ClaudePromptHandler'; import {Plugin, System, User} from '@support/server_api'; import {siteOneUrl} from '@support/test_config'; +const execFileAsync = promisify(execFile); + // Number of retry attempts const MAX_RETRY_ATTEMPTS = 3; @@ -14,6 +19,23 @@ const RETRY_DELAY = 5000; let isFirstLaunch = true; +async function runAdbCommand(args: string[]): Promise { + try { + await execFileAsync('adb', args); + } catch (error) { + console.warn(`Android system UI setup command failed: adb ${args.join(' ')}: ${(error as Error).message}`); + } +} + +async function prepareAndroidSystemUi(): Promise { + if (device.getPlatform() !== 'android') { + return; + } + + await runAdbCommand(['shell', 'cmd', 'statusbar', 'collapse']); + await runAdbCommand(['shell', 'settings', 'put', 'secure', 'autofill_service', 'null']); +} + /** * Verify Detox connection to app is healthy * @param maxAttempts - Maximum number of verification attempts @@ -76,6 +98,8 @@ export async function launchAppWithRetry(): Promise { for (let attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) { try { + await prepareAndroidSystemUi(); + if (isFirstLaunch) { // For first launch, clean install await device.launchApp({ @@ -109,6 +133,7 @@ export async function launchAppWithRetry(): Promise { }); } + await prepareAndroidSystemUi(); console.info(`✅ App launched successfully on attempt ${attempt}`); return;