MOB-948 add programmatic test user reset step to e2e

This commit is contained in:
Ryan Stelly
2025-10-21 14:36:57 -05:00
parent 3067d332e8
commit dd532da82e
4 changed files with 148 additions and 25 deletions

View File

@@ -1,6 +1,10 @@
import { exec, execSync } from "child_process";
import resetUserForTesting from "./sharedFlows/resetUserForTesting";
export async function iNatE2eBeforeAll( device ) {
await resetUserForTesting();
if ( device.getPlatform() === "android" ) {
await device.launchApp( {
newInstance: true,
@@ -109,6 +113,8 @@ export function terminateApp( deviceId, bundleId ) {
}
export async function iNatE2eAfterEach( device ) {
await resetUserForTesting();
if ( device && device.getPlatform() === "android" ) {
return;
}

View File

@@ -1,20 +0,0 @@
import {
by, element, waitFor
} from "detox";
const TIMEOUT = 10_000;
export default async function dismissAnnouncements() {
try {
// wait briefly to see if the announcement appears
await waitFor( element( by.id( "announcements-container" ) ) )
.toBeVisible()
.withTimeout( TIMEOUT );
// if we get here, the announcement is visible, so dismiss it
await element( by.id( "announcements-dismiss" ) ).tap();
} catch ( error ) {
// if timeout occurs, the element isn't visible, so continue with test
console.log( "No announcement present, continuing with test", error );
}
}

View File

@@ -0,0 +1,142 @@
import { create } from "apisauce";
import inatjs from "inaturalistjs";
import Config from "react-native-config-node";
import * as uuid from "uuid";
const apiHost = Config.OAUTH_API_URL;
const testUsernameAllowlist = [
"inaturalist-test"
];
const userAgent = "iNaturalistRN/e2e";
const installId = uuid.v4( ).toString( );
inatjs.setConfig( {
apiURL: Config.API_URL,
writeApiURL: Config.API_URL,
userAgent,
headers: {
"X-Installation-ID": installId
}
} );
// programatically dismisses announcements for user and deletes all existing observations
// in order to set up consistent testing conditions and remove need to wait for announcements
export default async function resetUserForTesting() {
console.log(
"Test user reset: dismissing announcements and deleting observations..."
);
if ( !testUsernameAllowlist.includes( Config.E2E_TEST_USERNAME ) ) {
const message = "This e2e test deletes observations of the user under test."
+ "Add this username to the `testUsernameAllowlist` if that's really what you want.";
throw new Error( message );
}
const apiClient = create( {
baseURL: apiHost,
headers: {
"User-Agent": userAgent,
"X-Installation-ID": installId
}
} );
await apiClient.get( "/logout" );
const formData = {
format: "json",
grant_type: "password",
client_id: Config.OAUTH_CLIENT_ID,
client_secret: Config.OAUTH_CLIENT_SECRET,
username: Config.E2E_TEST_USERNAME,
password: Config.E2E_TEST_PASSWORD,
locale: "en"
};
const tokenResponse = await apiClient.post( "/oauth/token", formData );
const accessToken = tokenResponse.data.access_token;
apiClient.setHeader( "Authorization", `Bearer ${accessToken}` );
const jwtResponse = await apiClient.get( "/users/api_token.json" );
const opts = {
api_token: jwtResponse.data.api_token
};
const announcementSearchParams = {
placement: "mobile",
locale: "en-US",
per_page: 20,
fields: {
id: true,
body: true,
dismissible: true,
start: true,
placement: true
}
};
const announcementResponse = await inatjs.announcements.search(
announcementSearchParams,
opts
);
const announcementIdsToDismiss = announcementResponse
.results
.filter( a => a.dismissable )
.map( a => a.id );
console.log( `Dismissing ${announcementIdsToDismiss.length} announcements` );
await Promise.all( announcementIdsToDismiss.map( async id => {
await inatjs.announcements.dismiss(
{ id },
opts
);
} ) );
const usersEditResponse = await apiClient.get(
"/users/edit.json",
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"User-Agent": userAgent
}
}
);
const userId = usersEditResponse.data.id;
const observationResponse = await inatjs.observations.search( { user_id: userId }, opts );
// is this _really_ an e2e test user if they have more than expected observations?
const suspiciousObservationThreshold = 10;
if ( typeof observationResponse.total_results !== "number"
|| observationResponse.total_results > suspiciousObservationThreshold ) {
const message
= `More than ${suspiciousObservationThreshold} observations found for test user. Aborting.`;
throw new Error( message );
}
const observationIdsToDelete = observationResponse.results.map( a => a.uuid );
console.log( `Deleting ${observationIdsToDelete.length} observations` );
await Promise.all( observationIdsToDelete.map( async uuid => {
console.log( { uuid } );
const deleteResponse = await inatjs.observations.delete(
{ uuid },
opts
);
console.log( { uuid, deleteResponse, stringify: JSON.stringify( deleteResponse ) } );
} ) );
await apiClient.get( "/logout" );
console.log(
"Test user reset: announcements dismissed and observations deleted"
);
}

View File

@@ -3,7 +3,6 @@ import {
} from "detox";
import Config from "react-native-config-node";
import dismissAnnouncements from "./dismissAnnouncements";
import switchPowerMode from "./switchPowerMode";
const TIMEOUT = 10_000;
@@ -42,9 +41,5 @@ export default async function signIn() {
await waitFor( username ).toBeVisible().withTimeout( TIMEOUT );
await expect( username ).toBeVisible();
/*
Dismiss announcements if they're blocking the UI
*/
await dismissAnnouncements();
return username;
}