diff --git a/.detoxrc.js b/.detoxrc.js index e8ff3ea..1143c10 100644 --- a/.detoxrc.js +++ b/.detoxrc.js @@ -47,7 +47,7 @@ module.exports = { type: 'android.emulator', device: { avdName: process.env.TEST_ANDROID_EMU - } + }, } }, configurations: { @@ -69,6 +69,23 @@ module.exports = { } }, custom: { - defaultTestTimeout: 5000 + defaultTestTimeout: 15000 + }, + artifacts: { + rootDir: "./screenshots/", + plugins: { + log: {"enabled": true}, + uiHierarchy: {"enabled": true}, + screenshot: { + keepOnlyFailedTestsArtifacts: false, + takeWhen: { + "testStart": true, + "testDone": true + } + }, + video: { + enabled: true + } + } } }; diff --git a/.env.sample b/.env.sample index e000bca..0f3773f 100644 --- a/.env.sample +++ b/.env.sample @@ -8,3 +8,5 @@ TEST_IOS_SIM= # e.g. 'Android_34' or 'Pixel_3a_API_34' TEST_ANDROID_EMU= SIGNING_CONFIG_PATH=/absolute/path/to/android-config/signing-config.gradle +KEYSTORE_PATH=/absolute/path/to/wallet.keystore +KEYSTORE_PASSWORD= diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df489c3..9c7480d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,4 +41,4 @@ jobs: uses: kitabisa/sonarqube-action@v1.2.1 with: host: ${{ secrets.SONARQUBE_HOST }} - login: ${{ secrets.SONARQUBE_DEV_INRUPT_COM_GITHUB_TOKEN }} \ No newline at end of file + login: ${{ secrets.SONARQUBE_DEV_INRUPT_COM_GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 033019e..c8e4d0f 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ expo-env.d.ts .env *.keystore + +# Folder where the UI tests get stored. +screenshots/ diff --git a/README.md b/README.md index c08fbf9..d7d599d 100644 --- a/README.md +++ b/README.md @@ -64,14 +64,23 @@ keytool -genkeypair -v -storetype PKCS12 \ -noprompt -dname "CN=wallet.example.com" ``` -Add the following to: `~/.gradle/gradle.properties` and update the placeholders. +Add the following to: `.env` and update the placeholders. ```text -WALLET_UPLOAD_STORE_FILE=/inrupt-wallet-frontend/android/app/wallet.keystore -WALLET_UPLOAD_STORE_PASSWORD= -WALLET_UPLOAD_KEY_ALIAS=wallet -WALLET_UPLOAD_KEY_PASSWORD= +KEYSTORE_PATH=/inrupt-wallet-frontend/android/app/wallet.keystore +KEYSTORE_PASSWORD= ``` +#### Make the keystore available to CI + +In order to make the keystore available to CI, it has to be present in the repository secret. +To +- Encrypting the keystore with a GPG key to get a Base64 representation: `gpg -c --armor wallet.keystore` +- Create Github repository secrets: + - ENCRYPTED_KEYSTORE with the Base64-encoded encrypted keystore + - KEYSTORE_DECRYPTION_KEY with the GPG key + - KEYSTORE_PASSWORD with the keystore password +- In CI, decrypt the keystore back: `gpg -d --passphrase "..." --batch wallet.keystore.asc > wallet.keystore` + ## Running the application If you are going to run the application in an emulator or simulator, you need to build the development version using @@ -104,7 +113,7 @@ The tests require access credentials for a Pod which will be used by this instan Make a copy of the provided `.env.sample` named `.env`, and replace placeholders with actual values specific to your setup. -#### Running the tests on iOS +### Running the tests on iOS To build the iOS wallet app in an iOS simulator, just run the following command: @@ -134,7 +143,7 @@ Execute the command below to start Detox test on iOS. npx detox test --configuration=ios.sim.release ``` -#### Running the tests on Android +### Running the tests on Android Ensure that a virtual device has been added to the Android emulator. diff --git a/android-config/signing-config.gradle b/android-config/signing-config.gradle index 0d6dd16..177b7fc 100644 --- a/android-config/signing-config.gradle +++ b/android-config/signing-config.gradle @@ -1,11 +1,11 @@ android { signingConfigs { release { - if (project.hasProperty('WALLET_UPLOAD_STORE_FILE')) { - storeFile file(WALLET_UPLOAD_STORE_FILE) - storePassword WALLET_UPLOAD_STORE_PASSWORD - keyAlias WALLET_UPLOAD_KEY_ALIAS - keyPassword WALLET_UPLOAD_KEY_PASSWORD + if (project.hasProperty('inrupt.wallet.frontend.keystore.file')) { + storeFile file(project.property('inrupt.wallet.frontend.keystore.file')) + storePassword project.property('inrupt.wallet.frontend.keystore.password') + keyAlias project.property('inrupt.wallet.frontend.key.alias') + keyPassword project.property('inrupt.wallet.frontend.key.password') } } } diff --git a/e2e/pages/LoginPage.ts b/e2e/pages/LoginPage.ts index ffba44d..908fb5e 100644 --- a/e2e/pages/LoginPage.ts +++ b/e2e/pages/LoginPage.ts @@ -19,6 +19,7 @@ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // import { by, element, expect, web } from "detox"; + import detoxConfig from "../../.detoxrc"; import HomePage from "./HomePage"; import PermissionPage from "./PermissionPage"; @@ -58,20 +59,18 @@ class LoginPage { // Perform login await expect(this.signInFormUsernameInput).toExist(); - await this.signInFormUsernameInput.runScript(`(element) => { - element.value = "${username}"; - }`); + await this.signInFormUsernameInput.replaceText(username); await expect(this.signInFormPasswordInput).toExist(); - await this.signInFormPasswordInput.runScript(`(element) => { - element.value = "${password}"; - }`); + await this.signInFormPasswordInput.replaceText(password); - await expect(this.signInSubmitButton).toExist(); - await this.signInSubmitButton.runScript((buttonElement) => { - buttonElement.click(); + await new Promise((resolve) => { + setTimeout(resolve, timeout * 5); }); + await expect(this.signInSubmitButton).toExist(); + await this.signInSubmitButton.tap(); + // Handle permission screen await new Promise((resolve) => { setTimeout(resolve, timeout); diff --git a/e2e/pages/PermissionPage.ts b/e2e/pages/PermissionPage.ts index b556201..2ffe25d 100644 --- a/e2e/pages/PermissionPage.ts +++ b/e2e/pages/PermissionPage.ts @@ -18,20 +18,21 @@ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // + +import { expect, web, by } from "detox"; + class PermissionPage { private continueButton: Detox.WebElement; constructor() { this.continueButton = web.element( - by.web.xpath('//button[text()="Continue"]') + by.web.cssSelector('button[data-testid="prompt-continue"]') ); } async clickContinueButton() { - // await waitFor(this.continueButton).toExist().withTimeout(5000); - await this.continueButton.runScript((element: HTMLButtonElement) => { - element.click(); - }); + await expect(this.continueButton).toExist(); + await this.continueButton.tap(); } } diff --git a/plugins/withSigningConfig.js b/plugins/withSigningConfig.js index d2a265b..fec78fc 100644 --- a/plugins/withSigningConfig.js +++ b/plugins/withSigningConfig.js @@ -28,18 +28,48 @@ module.exports = function withSigningConfig(config) { // Add path to gradle signing snippet to gradle.properties. // This is then used by the android/app/build.gradle mod. withGradleProperties(config, async (gradleProps) => { + if (process.env.SIGNING_CONFIG_PATH === undefined) { + throw new Error("Missing environment variable SIGNING_CONFIG_PATH"); + } gradleProps.modResults.push({ type: "comment", value: "Path to the signing configuration gradle snippet", }); - if (process.env.SIGNING_CONFIG_PATH === undefined) { - throw new Error("Missing environment variable SIGNING_CONFIG_PATH"); - } gradleProps.modResults.push({ type: "property", key: "inrupt.wallet.frontend.signing", value: process.env.SIGNING_CONFIG_PATH, }); + if (process.env.KEYSTORE_PATH === undefined) { + throw new Error("Missing environment variable KEYSTORE_PATH"); + } + if (process.env.KEYSTORE_PASSWORD === undefined) { + throw new Error("Missing environment variable KEYSTORE_PASSWORD"); + } + gradleProps.modResults.push({ + type: "comment", + value: "Keystore configuration for app signing.", + }); + gradleProps.modResults.push({ + type: "property", + key: "inrupt.wallet.frontend.keystore.file", + value: process.env.KEYSTORE_PATH, + }); + gradleProps.modResults.push({ + type: "property", + key: "inrupt.wallet.frontend.keystore.password", + value: process.env.KEYSTORE_PASSWORD, + }); + gradleProps.modResults.push({ + type: "property", + key: "inrupt.wallet.frontend.key.alias", + value: "wallet", + }); + gradleProps.modResults.push({ + type: "property", + key: "inrupt.wallet.frontend.key.password", + value: process.env.KEYSTORE_PASSWORD, + }); return gradleProps; }); // Append a block to android/app/build.gradle to reuse signing config