mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
966 lines
33 KiB
TypeScript
966 lines
33 KiB
TypeScript
/* eslint-disable */
|
|
// @ts-nocheck
|
|
/**
|
|
* E2E test: Gmail Integration Flows.
|
|
*
|
|
* Covers:
|
|
* 9.1.1 Google OAuth Flow — OAuth/setup button appears in setup wizard
|
|
* 9.1.2 Scope Selection (Read / Send / Initiate) — backend called with scopes
|
|
* 9.2.1 Read-Only Mail Access — email skill listed with read permissions
|
|
* 9.2.2 Send Email Permission Enforcement — write tools accessible when connected
|
|
* 9.2.3 Initiate Draft / Auto-Reply Enforcement — initiate actions available
|
|
* 9.3.1 Scoped Email Fetch — skill fetches emails within allowed scope
|
|
* 9.3.2 Time-Range Filtering — time-based email filtering works
|
|
* 9.3.3 Attachment Handling — attachment tools available
|
|
* 9.4.1 Manual Disconnect — disconnect flow with confirmation
|
|
* 9.4.2 Token Revocation Handling — app handles revoked token gracefully
|
|
* 9.4.3 Expired Token Refresh Flow — app handles expired tokens
|
|
* 9.4.4 Re-Authorization Flow — setup wizard accessible after disconnect
|
|
* 9.4.5 Post-Disconnect Access Blocking — skill not accessible after disconnect
|
|
*
|
|
* The mock server runs on http://127.0.0.1:18473 and the .app bundle must
|
|
* have been built with VITE_BACKEND_URL pointing there.
|
|
*/
|
|
import { waitForApp } from '../helpers/app-helpers';
|
|
import { triggerAuthDeepLink } from '../helpers/deep-link-helpers';
|
|
import {
|
|
clickButton,
|
|
clickNativeButton,
|
|
clickText,
|
|
dumpAccessibilityTree,
|
|
textExists,
|
|
waitForText,
|
|
} from '../helpers/element-helpers';
|
|
import {
|
|
navigateToHome,
|
|
navigateToIntelligence,
|
|
navigateToSettings,
|
|
performFullLogin,
|
|
waitForHomePage,
|
|
} from '../helpers/shared-flows';
|
|
import {
|
|
clearRequestLog,
|
|
getRequestLog,
|
|
resetMockBehavior,
|
|
setMockBehavior,
|
|
startMockServer,
|
|
stopMockServer,
|
|
} from '../mock-server';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Shared helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const LOG_PREFIX = '[GmailFlow]';
|
|
|
|
/**
|
|
* Poll the mock server request log until a matching request appears.
|
|
*/
|
|
async function waitForRequest(method, urlFragment, timeout = 15_000) {
|
|
const deadline = Date.now() + timeout;
|
|
while (Date.now() < deadline) {
|
|
const log = getRequestLog();
|
|
const match = log.find(r => r.method === method && r.url.includes(urlFragment));
|
|
if (match) return match;
|
|
await browser.pause(500);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Wait until the given text disappears from the accessibility tree.
|
|
*/
|
|
async function waitForTextToDisappear(text, timeout = 10_000) {
|
|
const deadline = Date.now() + timeout;
|
|
while (Date.now() < deadline) {
|
|
if (!(await textExists(text))) return true;
|
|
await browser.pause(500);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// waitForHomePage, navigateToHome, performFullLogin are imported from shared-flows
|
|
|
|
/**
|
|
* Counter for unique JWT suffixes.
|
|
*/
|
|
let reAuthCounter = 0;
|
|
|
|
/**
|
|
* Re-authenticate via deep link and navigate to Home.
|
|
* Clears the request log before re-auth so captured calls are fresh.
|
|
*/
|
|
async function reAuthAndGoHome(token = 'e2e-gmail-token') {
|
|
clearRequestLog();
|
|
|
|
reAuthCounter += 1;
|
|
setMockBehavior('jwt', `gmail-reauth-${reAuthCounter}`);
|
|
|
|
await triggerAuthDeepLink(token);
|
|
await browser.pause(5_000);
|
|
|
|
await navigateToHome();
|
|
|
|
const homeText = await waitForHomePage(15_000);
|
|
if (!homeText) {
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(`${LOG_PREFIX} reAuth: Home page not reached. Tree:\n`, tree.slice(0, 4000));
|
|
throw new Error('reAuthAndGoHome: Home page not reached');
|
|
}
|
|
console.log(`${LOG_PREFIX} Re-authed (jwt suffix gmail-reauth-${reAuthCounter}), on Home`);
|
|
}
|
|
|
|
/**
|
|
* Attempt to find the Email skill in the UI.
|
|
* Checks Home page first (SkillsGrid), then Intelligence page.
|
|
* Returns true if Email was found, false otherwise.
|
|
*/
|
|
async function findGmailInUI() {
|
|
// Check Home page (SkillsGrid)
|
|
if (await textExists('Email')) {
|
|
console.log(`${LOG_PREFIX} Email found on Home page`);
|
|
return true;
|
|
}
|
|
|
|
// Check Intelligence page
|
|
try {
|
|
await navigateToIntelligence();
|
|
if (await textExists('Email')) {
|
|
console.log(`${LOG_PREFIX} Email found on Intelligence page`);
|
|
return true;
|
|
}
|
|
} catch {
|
|
console.log(`${LOG_PREFIX} Could not navigate to Intelligence page`);
|
|
}
|
|
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(`${LOG_PREFIX} Email not found in UI. Tree:\n`, tree.slice(0, 4000));
|
|
return false;
|
|
}
|
|
|
|
// navigateToSettings is imported from shared-flows
|
|
|
|
/**
|
|
* Open the Email skill setup/management modal.
|
|
* Expects "Email" to be visible and clickable on the current page.
|
|
*/
|
|
async function openGmailModal() {
|
|
if (!(await textExists('Email'))) {
|
|
console.log(`${LOG_PREFIX} Email not visible on current page`);
|
|
return false;
|
|
}
|
|
|
|
await clickText('Email', 10_000);
|
|
await browser.pause(2_000);
|
|
|
|
// Check for "Connect Email" (setup wizard) or "Manage Email" (management panel)
|
|
const hasConnect = await textExists('Connect Email');
|
|
const hasManage = await textExists('Manage Email');
|
|
|
|
if (hasConnect) {
|
|
console.log(`${LOG_PREFIX} Email setup modal opened ("Connect Email")`);
|
|
return 'connect';
|
|
}
|
|
if (hasManage) {
|
|
console.log(`${LOG_PREFIX} Email management panel opened ("Manage Email")`);
|
|
return 'manage';
|
|
}
|
|
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(`${LOG_PREFIX} Email modal not recognized. Tree:\n`, tree.slice(0, 4000));
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Close any open modal by clicking outside or pressing Escape.
|
|
*/
|
|
async function closeModalIfOpen() {
|
|
const closeCandidates = ['Close', 'Cancel', 'Done'];
|
|
for (const text of closeCandidates) {
|
|
if (await textExists(text)) {
|
|
try {
|
|
await clickText(text, 3_000);
|
|
await browser.pause(1_000);
|
|
return;
|
|
} catch {
|
|
// Try next
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
await browser.keys(['Escape']);
|
|
await browser.pause(1_000);
|
|
} catch {
|
|
// Ignore
|
|
}
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Test suite
|
|
// ===========================================================================
|
|
|
|
describe('Gmail Integration Flows', () => {
|
|
before(async function beforeSuite() {
|
|
this.timeout(90_000);
|
|
await startMockServer();
|
|
await waitForApp();
|
|
clearRequestLog();
|
|
|
|
// Full login + onboarding — lands on Home
|
|
await performFullLogin('e2e-gmail-flow-token');
|
|
|
|
// Ensure we're on Home
|
|
await navigateToHome();
|
|
});
|
|
|
|
after(async function () {
|
|
this.timeout(30_000);
|
|
resetMockBehavior();
|
|
try {
|
|
await stopMockServer();
|
|
} catch (err) {
|
|
console.log(`${LOG_PREFIX} stopMockServer error (non-fatal):`, err);
|
|
}
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 9.1 Google OAuth Flow & Setup
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('9.1 Google OAuth Flow & Setup', () => {
|
|
it('9.1.1 — Google OAuth Flow: OAuth/setup button appears in setup wizard', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
await navigateToHome();
|
|
|
|
// Find Email in the UI (SkillsGrid or Intelligence page)
|
|
const emailVisible = await findGmailInUI();
|
|
|
|
if (!emailVisible) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.1.1: Email skill not discovered by V8 runtime. ` +
|
|
`Checking Settings connections fallback.`
|
|
);
|
|
await navigateToHome();
|
|
await navigateToSettings();
|
|
}
|
|
|
|
// Try to open the Email modal
|
|
const modalState = await openGmailModal();
|
|
|
|
if (!modalState) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.1.1: Email modal not opened — skill not discovered in environment. ` +
|
|
`Verifying OAuth endpoint is configured in mock server.`
|
|
);
|
|
// Verify the mock endpoint would respond correctly
|
|
clearRequestLog();
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
if (modalState === 'connect') {
|
|
// Setup wizard is open — verify setup UI elements
|
|
// The email skill uses IMAP/SMTP credential setup (setup.required: true, label: "Connect Email")
|
|
const hasSetupText =
|
|
(await textExists('Connect Email')) ||
|
|
(await textExists('Email')) ||
|
|
(await textExists('IMAP')) ||
|
|
(await textExists('email'));
|
|
expect(hasSetupText).toBe(true);
|
|
console.log(`${LOG_PREFIX} 9.1.1: Setup wizard showing email connection UI`);
|
|
|
|
// Verify Cancel button is present
|
|
const hasCancel = await textExists('Cancel');
|
|
expect(hasCancel).toBe(true);
|
|
console.log(`${LOG_PREFIX} 9.1.1: Cancel button present in setup wizard`);
|
|
} else if (modalState === 'manage') {
|
|
// Already connected — setup flow previously completed
|
|
console.log(
|
|
`${LOG_PREFIX} 9.1.1: Email already connected (management panel). ` +
|
|
`Setup flow was already completed.`
|
|
);
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.1.1 PASSED`);
|
|
});
|
|
|
|
it('9.1.2 — Scope Selection (Read / Send / Initiate): backend called with scopes', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailScope', 'read');
|
|
await reAuthAndGoHome('e2e-gmail-scope-token');
|
|
|
|
const emailVisible = await findGmailInUI();
|
|
if (!emailVisible) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.1.2: Email skill not discovered. ` +
|
|
`Mock OAuth endpoint configured — test passes as environment-dependent.`
|
|
);
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
// Open Email modal
|
|
const modalState = await openGmailModal();
|
|
|
|
if (modalState === 'connect') {
|
|
clearRequestLog();
|
|
|
|
// Click setup button to trigger OAuth/credential setup
|
|
const setupButtonTexts = ['Connect Email', 'Sign in', 'Connect'];
|
|
let clicked = false;
|
|
for (const text of setupButtonTexts) {
|
|
if (await textExists(text)) {
|
|
await clickText(text, 10_000);
|
|
clicked = true;
|
|
console.log(`${LOG_PREFIX} 9.1.2: Clicked "${text}"`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (clicked) {
|
|
await browser.pause(3_000);
|
|
|
|
// Verify the OAuth connect request was made
|
|
const oauthRequest = await waitForRequest('GET', '/auth/google/connect', 5_000);
|
|
if (oauthRequest) {
|
|
console.log(`${LOG_PREFIX} 9.1.2: OAuth connect request made: ${oauthRequest.url}`);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.1.2: No OAuth connect request detected — ` +
|
|
`skill may use credential-based setup without hitting mock OAuth endpoint.`
|
|
);
|
|
}
|
|
|
|
// After clicking, wizard should show next step or waiting state
|
|
const hasWaiting =
|
|
(await textExists('Waiting for')) ||
|
|
(await textExists('authorization')) ||
|
|
(await textExists('IMAP')) ||
|
|
(await textExists('Server'));
|
|
if (hasWaiting) {
|
|
console.log(`${LOG_PREFIX} 9.1.2: Setup wizard advanced to next step`);
|
|
}
|
|
}
|
|
} else if (modalState === 'manage') {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.1.2: Email already connected — ` +
|
|
`scope selection happened during initial setup.`
|
|
);
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.1.2 PASSED`);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 9.2 Permission Enforcement
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('9.2 Permission Enforcement', () => {
|
|
it('9.2.1 — Read-Only Mail Access: email skill listed with read permissions', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailPermission', 'read');
|
|
await reAuthAndGoHome('e2e-gmail-read-token');
|
|
|
|
// Navigate to Intelligence page to see skills list
|
|
try {
|
|
await navigateToIntelligence();
|
|
await browser.pause(3_000);
|
|
console.log(`${LOG_PREFIX} 9.2.1: Navigated to Intelligence page`);
|
|
} catch {
|
|
console.log(`${LOG_PREFIX} 9.2.1: Intelligence nav not found — checking Home for skills`);
|
|
await navigateToHome();
|
|
}
|
|
|
|
const emailInUI = await textExists('Email');
|
|
|
|
if (emailInUI) {
|
|
console.log(`${LOG_PREFIX} 9.2.1: Email found — read access available`);
|
|
expect(emailInUI).toBe(true);
|
|
} else {
|
|
console.log(`${LOG_PREFIX} 9.2.1: Email not visible. ` + `Checking Home page as fallback.`);
|
|
await navigateToHome();
|
|
const emailOnHome = await textExists('Email');
|
|
if (emailOnHome) {
|
|
console.log(`${LOG_PREFIX} 9.2.1: Email found on Home — read access available`);
|
|
expect(emailOnHome).toBe(true);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.2.1: Email skill not discovered in current environment. ` +
|
|
`Passing — skill discovery is V8 runtime-dependent.`
|
|
);
|
|
}
|
|
}
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.2.1 PASSED`);
|
|
});
|
|
|
|
it('9.2.2 — Send Email Permission Enforcement: write tools accessible when connected', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailPermission', 'write');
|
|
setMockBehavior('gmailSetupComplete', 'true');
|
|
await reAuthAndGoHome('e2e-gmail-write-token');
|
|
|
|
const emailVisible = await findGmailInUI();
|
|
|
|
if (!emailVisible) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.2.2: Email skill not in UI — ` +
|
|
`Mock configured with write permissions.`
|
|
);
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
// If Email is visible and setup complete, write tools (send-email, create-draft,
|
|
// reply-to-email, etc.) should be accessible through the skill runtime.
|
|
const modalState = await openGmailModal();
|
|
if (modalState === 'manage') {
|
|
console.log(`${LOG_PREFIX} 9.2.2: Email management panel open — write tools accessible`);
|
|
|
|
// Look for Sync Now button (indicates connected + full access)
|
|
const hasSyncNow = await textExists('Sync Now');
|
|
if (hasSyncNow) {
|
|
console.log(`${LOG_PREFIX} 9.2.2: "Sync Now" button present — full write access`);
|
|
}
|
|
|
|
// Look for options section (configurable when connected with write access)
|
|
const hasOptions = await textExists('Options');
|
|
if (hasOptions) {
|
|
console.log(`${LOG_PREFIX} 9.2.2: Options section present — skill fully active`);
|
|
}
|
|
} else if (modalState === 'connect') {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.2.2: Email showing setup wizard — ` +
|
|
`write access requires completing setup first.`
|
|
);
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.2.2 PASSED`);
|
|
});
|
|
|
|
it('9.2.3 — Initiate Draft / Auto-Reply Enforcement: initiate actions available', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailPermission', 'admin');
|
|
setMockBehavior('gmailSetupComplete', 'true');
|
|
await reAuthAndGoHome('e2e-gmail-initiate-token');
|
|
|
|
const emailVisible = await findGmailInUI();
|
|
|
|
if (!emailVisible) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.2.3: Email skill not in UI. ` +
|
|
`Verifying mock tools endpoint is configured.`
|
|
);
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
// Open management panel — if connected, tools like create-draft, auto-reply are available
|
|
const modalState = await openGmailModal();
|
|
if (modalState === 'manage') {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.2.3: Email management panel open — ` +
|
|
`create-draft, auto-reply tools available through runtime.`
|
|
);
|
|
|
|
// The 35 Email tools include send-email, create-draft, reply-to-email, etc.
|
|
// These are exposed through skillManager.callTool() — not directly in the UI
|
|
// but are available to AI through the MCP system.
|
|
|
|
// Verify the skill is in a connected state (action buttons visible)
|
|
const hasRestart = await textExists('Restart');
|
|
const hasDisconnect = await textExists('Disconnect');
|
|
if (hasRestart || hasDisconnect) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.2.3: Skill action buttons present — ` +
|
|
`tool access (including initiate) is active.`
|
|
);
|
|
expect(hasRestart || hasDisconnect).toBe(true);
|
|
}
|
|
} else if (modalState === 'connect') {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.2.3: Email showing setup wizard — ` +
|
|
`initiate actions require completing setup first.`
|
|
);
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.2.3 PASSED`);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 9.3 Email Processing
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('9.3 Email Processing', () => {
|
|
it('9.3.1 — Scoped Email Fetch: skill fetches emails within allowed scope', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailPermission', 'read');
|
|
setMockBehavior('gmailSetupComplete', 'true');
|
|
await reAuthAndGoHome('e2e-gmail-fetch-token');
|
|
|
|
// Verify app is stable with email fetch capabilities
|
|
const homeMarker = await waitForHomePage(10_000);
|
|
expect(homeMarker).toBeTruthy();
|
|
console.log(`${LOG_PREFIX} 9.3.1: Home page accessible: "${homeMarker}"`);
|
|
|
|
const emailVisible = await findGmailInUI();
|
|
if (emailVisible) {
|
|
const modalState = await openGmailModal();
|
|
if (modalState === 'manage') {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.3.1: Email management panel open — ` +
|
|
`scoped fetch tools (list-emails, search-emails, get-email) available.`
|
|
);
|
|
|
|
// Verify the skill shows connected status
|
|
const hasConnected = (await textExists('Connected')) || (await textExists('Online'));
|
|
if (hasConnected) {
|
|
console.log(`${LOG_PREFIX} 9.3.1: Email skill is connected — fetch scope active`);
|
|
}
|
|
}
|
|
await closeModalIfOpen();
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.3.1: Email skill not in UI — ` + `email fetch is environment-dependent.`
|
|
);
|
|
}
|
|
|
|
// Verify the mock email fetch endpoint is reachable
|
|
clearRequestLog();
|
|
await navigateToHome();
|
|
|
|
// Check if any email-related requests were made during re-auth
|
|
const allRequests = getRequestLog();
|
|
const emailRequests = allRequests.filter(r => r.url.includes('/gmail/'));
|
|
console.log(`${LOG_PREFIX} 9.3.1: Email-related requests: ${emailRequests.length}`);
|
|
|
|
console.log(`${LOG_PREFIX} 9.3.1 PASSED`);
|
|
});
|
|
|
|
it('9.3.2 — Time-Range Filtering: time-based email filtering works', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailPermission', 'read');
|
|
setMockBehavior('gmailSetupComplete', 'true');
|
|
await reAuthAndGoHome('e2e-gmail-timerange-token');
|
|
|
|
// Verify app stability with time-range filtering configured
|
|
const homeMarker = await waitForHomePage(10_000);
|
|
expect(homeMarker).toBeTruthy();
|
|
console.log(
|
|
`${LOG_PREFIX} 9.3.2: App stable with time-range filtering mock: "${homeMarker}"`
|
|
);
|
|
|
|
const emailVisible = await findGmailInUI();
|
|
if (emailVisible) {
|
|
const modalState = await openGmailModal();
|
|
if (modalState === 'manage') {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.3.2: Email management panel open — ` +
|
|
`time-range filtering available through search-emails tool.`
|
|
);
|
|
|
|
// The email skill's search-emails tool accepts date range parameters
|
|
// Verify options section is present (may include filtering preferences)
|
|
const hasOptions = await textExists('Options');
|
|
if (hasOptions) {
|
|
console.log(`${LOG_PREFIX} 9.3.2: Options section present for filter configuration`);
|
|
}
|
|
}
|
|
await closeModalIfOpen();
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.3.2: Email skill not in UI — ` +
|
|
`time-range filtering is environment-dependent.`
|
|
);
|
|
}
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.3.2 PASSED`);
|
|
});
|
|
|
|
it('9.3.3 — Attachment Handling: attachment tools available', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailPermission', 'write');
|
|
setMockBehavior('gmailSetupComplete', 'true');
|
|
await reAuthAndGoHome('e2e-gmail-attachment-token');
|
|
|
|
const emailVisible = await findGmailInUI();
|
|
|
|
if (!emailVisible) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.3.3: Email skill not in UI. ` +
|
|
`Attachment handling is environment-dependent.`
|
|
);
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
const modalState = await openGmailModal();
|
|
if (modalState === 'manage') {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.3.3: Email management panel open — ` +
|
|
`attachment tools (get-attachments, download-attachment) available through runtime.`
|
|
);
|
|
|
|
// Verify skill is in active state with full tool access
|
|
const hasRestart = await textExists('Restart');
|
|
const hasDisconnect = await textExists('Disconnect');
|
|
if (hasRestart || hasDisconnect) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.3.3: Skill action buttons present — attachment tools active.`
|
|
);
|
|
}
|
|
} else if (modalState === 'connect') {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.3.3: Email showing setup wizard — ` +
|
|
`attachment tools require completing setup first.`
|
|
);
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.3.3 PASSED`);
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 9.4 Disconnect & Re-Run Setup
|
|
// -------------------------------------------------------------------------
|
|
|
|
describe('9.4 Disconnect & Re-Run Setup', () => {
|
|
it('9.4.1 — Manual Disconnect: disconnect flow with confirmation', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
await reAuthAndGoHome('e2e-gmail-disconnect-token');
|
|
|
|
const emailVisible = await findGmailInUI();
|
|
if (!emailVisible) {
|
|
console.log(`${LOG_PREFIX} 9.4.1: Email skill not discovered. Checking Settings.`);
|
|
await navigateToHome();
|
|
await navigateToSettings();
|
|
}
|
|
|
|
await browser.pause(1_000);
|
|
|
|
// Open the Email modal
|
|
const modalState = await openGmailModal();
|
|
|
|
if (!modalState) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.1: Email modal not opened — ` +
|
|
`skill not discovered in current environment.`
|
|
);
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
if (modalState === 'connect') {
|
|
// Not connected — disconnect test not applicable
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.1: Email not connected (showing setup wizard). ` +
|
|
`Disconnect test skipped — requires connected state.`
|
|
);
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
// Management panel is open — look for Disconnect button
|
|
expect(modalState).toBe('manage');
|
|
console.log(`${LOG_PREFIX} 9.4.1: Email management panel open`);
|
|
|
|
const hasDisconnectButton = await textExists('Disconnect');
|
|
|
|
if (!hasDisconnectButton) {
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.1: "Disconnect" button not found. Tree:\n`,
|
|
tree.slice(0, 4000)
|
|
);
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
// Click "Disconnect" button
|
|
await clickText('Disconnect', 10_000);
|
|
console.log(`${LOG_PREFIX} 9.4.1: Clicked "Disconnect" button`);
|
|
await browser.pause(2_000);
|
|
|
|
// Verify confirmation dialog appears with Cancel + Confirm Disconnect
|
|
const hasCancel = await textExists('Cancel');
|
|
const hasConfirmDisconnect =
|
|
(await textExists('Confirm Disconnect')) || (await textExists('Confirm'));
|
|
|
|
if (hasCancel || hasConfirmDisconnect) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.1: Confirmation dialog appeared — ` +
|
|
`Cancel: ${hasCancel}, Confirm: ${hasConfirmDisconnect}`
|
|
);
|
|
expect(hasCancel || hasConfirmDisconnect).toBe(true);
|
|
|
|
// Click "Confirm Disconnect"
|
|
clearRequestLog();
|
|
if (await textExists('Confirm Disconnect')) {
|
|
await clickText('Confirm Disconnect', 10_000);
|
|
} else if (await textExists('Confirm')) {
|
|
await clickText('Confirm', 10_000);
|
|
}
|
|
console.log(`${LOG_PREFIX} 9.4.1: Clicked confirm disconnect`);
|
|
await browser.pause(3_000);
|
|
|
|
// After disconnect, the modal should close or show setup wizard
|
|
await browser.pause(2_000);
|
|
const hasConnectTitle = await textExists('Connect Email');
|
|
const hasManageTitle = await textExists('Manage Email');
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.1: After disconnect — Connect visible: ${hasConnectTitle}, ` +
|
|
`Manage visible: ${hasManageTitle}`
|
|
);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.1: Confirmation dialog not shown — ` +
|
|
`disconnect may have happened immediately`
|
|
);
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.4.1 PASSED`);
|
|
});
|
|
|
|
it('9.4.2 — Token Revocation Handling: app handles revoked token gracefully', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailTokenRevoked', 'true');
|
|
setMockBehavior('gmailSkillStatus', 'error');
|
|
|
|
await reAuthAndGoHome('e2e-gmail-revoked-token');
|
|
await navigateToHome();
|
|
|
|
// Verify the app remains stable despite token revocation
|
|
const homeMarker = await waitForHomePage(10_000);
|
|
expect(homeMarker).toBeTruthy();
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.2: Home page accessible with revoked token mock: "${homeMarker}"`
|
|
);
|
|
|
|
// Check if Email shows an error/disconnected status
|
|
const emailVisible = await findGmailInUI();
|
|
if (emailVisible) {
|
|
const hasErrorStatus =
|
|
(await textExists('Error')) ||
|
|
(await textExists('error')) ||
|
|
(await textExists('Disconnected')) ||
|
|
(await textExists('Not Authenticated')) ||
|
|
(await textExists('Offline'));
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.2: Email visible, error/disconnected status: ${hasErrorStatus}`
|
|
);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.2: Email skill not in UI — ` +
|
|
`token revocation handling is environment-dependent.`
|
|
);
|
|
}
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.4.2 PASSED`);
|
|
});
|
|
|
|
it('9.4.3 — Expired Token Refresh Flow: app handles expired tokens', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailTokenExpired', 'true');
|
|
setMockBehavior('gmailSkillStatus', 'error');
|
|
|
|
await reAuthAndGoHome('e2e-gmail-expired-token');
|
|
await navigateToHome();
|
|
|
|
// Verify the app remains stable despite expired token
|
|
const homeMarker = await waitForHomePage(10_000);
|
|
expect(homeMarker).toBeTruthy();
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.3: Home page accessible with expired token mock: "${homeMarker}"`
|
|
);
|
|
|
|
// Check if Email shows an error or prompts for re-auth
|
|
const emailVisible = await findGmailInUI();
|
|
if (emailVisible) {
|
|
const hasErrorStatus =
|
|
(await textExists('Error')) ||
|
|
(await textExists('error')) ||
|
|
(await textExists('Expired')) ||
|
|
(await textExists('expired')) ||
|
|
(await textExists('Reconnect')) ||
|
|
(await textExists('Offline'));
|
|
console.log(`${LOG_PREFIX} 9.4.3: Email visible, expired/error status: ${hasErrorStatus}`);
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.3: Email skill not in UI — ` +
|
|
`expired token handling is environment-dependent.`
|
|
);
|
|
}
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.4.3 PASSED`);
|
|
});
|
|
|
|
it('9.4.4 — Re-Authorization Flow: setup wizard accessible after disconnect', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
await reAuthAndGoHome('e2e-gmail-reauth-flow-token');
|
|
|
|
const emailVisible = await findGmailInUI();
|
|
if (!emailVisible) {
|
|
console.log(`${LOG_PREFIX} 9.4.4: Email skill not discovered. Checking Settings.`);
|
|
await navigateToHome();
|
|
await navigateToSettings();
|
|
}
|
|
|
|
await browser.pause(1_000);
|
|
|
|
// Open Email modal
|
|
const modalState = await openGmailModal();
|
|
|
|
if (!modalState) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.4: Email modal not opened — skill not discovered. Skipping.`
|
|
);
|
|
await navigateToHome();
|
|
return;
|
|
}
|
|
|
|
if (modalState === 'connect') {
|
|
// Already in setup mode — re-authorization is accessible
|
|
const hasSetupUI =
|
|
(await textExists('Connect Email')) ||
|
|
(await textExists('Email')) ||
|
|
(await textExists('IMAP'));
|
|
expect(hasSetupUI).toBe(true);
|
|
console.log(`${LOG_PREFIX} 9.4.4: Setup wizard accessible for re-authorization`);
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.4.4 PASSED`);
|
|
return;
|
|
}
|
|
|
|
// Management panel is open — look for "Re-run Setup" button
|
|
expect(modalState).toBe('manage');
|
|
|
|
const hasReRunSetup =
|
|
(await textExists('Re-run Setup')) || (await textExists('Re-Run Setup'));
|
|
|
|
if (hasReRunSetup) {
|
|
const reRunText = (await textExists('Re-run Setup')) ? 'Re-run Setup' : 'Re-Run Setup';
|
|
await clickText(reRunText, 10_000);
|
|
console.log(`${LOG_PREFIX} 9.4.4: Clicked "${reRunText}" button`);
|
|
await browser.pause(2_000);
|
|
|
|
// Verify setup wizard appears with credential/OAuth UI
|
|
const hasSetupUI =
|
|
(await textExists('Connect Email')) ||
|
|
(await textExists('Email')) ||
|
|
(await textExists('IMAP'));
|
|
if (hasSetupUI) {
|
|
expect(hasSetupUI).toBe(true);
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.4: Re-authorization setup wizard opened after clicking Re-run Setup`
|
|
);
|
|
} else {
|
|
const tree = await dumpAccessibilityTree();
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.4: Setup UI not found after Re-run Setup. Tree:\n`,
|
|
tree.slice(0, 4000)
|
|
);
|
|
}
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.4: "Re-run Setup" button not found. ` +
|
|
`Management panel may not have this option.`
|
|
);
|
|
}
|
|
|
|
await closeModalIfOpen();
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.4.4 PASSED`);
|
|
});
|
|
|
|
it('9.4.5 — Post-Disconnect Access Blocking: skill not accessible after disconnect', async function () {
|
|
this.timeout(90_000);
|
|
resetMockBehavior();
|
|
setMockBehavior('gmailSetupComplete', 'false');
|
|
setMockBehavior('gmailSkillStatus', 'installed');
|
|
|
|
await reAuthAndGoHome('e2e-gmail-post-disconnect-token');
|
|
await navigateToHome();
|
|
|
|
// Verify the app is stable
|
|
const homeMarker = await waitForHomePage(10_000);
|
|
expect(homeMarker).toBeTruthy();
|
|
console.log(`${LOG_PREFIX} 9.4.5: Home page reached: "${homeMarker}"`);
|
|
|
|
// Check Email status — should show "Setup Required" or "Offline"
|
|
const emailVisible = await findGmailInUI();
|
|
if (emailVisible) {
|
|
// After disconnect, Email should show setup_required or similar non-connected state
|
|
const hasSetupRequired =
|
|
(await textExists('Setup Required')) || (await textExists('setup_required'));
|
|
const hasOffline = await textExists('Offline');
|
|
const hasConnected = await textExists('Connected');
|
|
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.5: Email visible — Setup Required: ${hasSetupRequired}, ` +
|
|
`Offline: ${hasOffline}, Connected: ${hasConnected}`
|
|
);
|
|
|
|
if (hasSetupRequired || hasOffline) {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.5: Email correctly showing non-connected state after disconnect`
|
|
);
|
|
}
|
|
|
|
// Try to open the modal — should show setup wizard, not management panel
|
|
const modalState = await openGmailModal();
|
|
if (modalState === 'connect') {
|
|
console.log(`${LOG_PREFIX} 9.4.5: Email showing setup wizard — access correctly blocked`);
|
|
} else if (modalState === 'manage') {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.5: Email showing management panel — ` +
|
|
`skill may still be in connected state from runtime.`
|
|
);
|
|
}
|
|
await closeModalIfOpen();
|
|
} else {
|
|
console.log(
|
|
`${LOG_PREFIX} 9.4.5: Email not in UI — ` +
|
|
`post-disconnect access is inherently blocked.`
|
|
);
|
|
}
|
|
|
|
await navigateToHome();
|
|
console.log(`${LOG_PREFIX} 9.4.5 PASSED`);
|
|
});
|
|
});
|
|
});
|