Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: webwallet e2e test for tx and sign #12

Merged
merged 21 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 15 additions & 17 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
E2E_REPO_TOKEN: ${{ secrets.E2E_REPO_TOKEN }}
E2E_REPO_OWNER: ${{ secrets.E2E_REPO_OWNER }}
E2E_REPO_RELEASE_NAME: ${{ secrets.E2E_REPO_RELEASE_NAME }}

WW_EMAIL: ${{ secrets.WW_EMAIL }}
WW_LOGIN_PASSWORD: ${{ secrets.WW_LOGIN_PASSWORD }}
EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }}
Expand All @@ -36,11 +36,11 @@ jobs:
with:
run_install: false
version: ${{ env.PNPM_VERSION }}

- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: "pnpm"
cache: "pnpm"

- name: Get pnpm store directory
shell: bash
Expand All @@ -65,17 +65,16 @@ jobs:
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}


key: ${{ github.sha }}

test-webwallet:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.48.2-jammy
needs: [build]
env:
ARGENT_X_ENVIRONMENT: "hydrogen"

WW_EMAIL: ${{ secrets.WW_EMAIL }}
WW_LOGIN_PASSWORD: ${{ secrets.WW_LOGIN_PASSWORD }}
EMAIL_PASSWORD: ${{ secrets.EMAIL_PASSWORD }}
Expand Down Expand Up @@ -108,12 +107,12 @@ jobs:
with:
path: ./*
key: ${{ github.sha }}

- name: Run e2e tests
run: |
pnpm run start & # Start the server in background
echo "Waiting for server to be ready..."
for i in {1..30}; do
for i in $(seq 1 30); do
if curl -s http://localhost:3000 > /dev/null; then
echo "Server is ready!"
break
Expand All @@ -126,7 +125,7 @@ jobs:
sleep 1
done
xvfb-run --auto-servernum pnpm test:webwallet

- name: Upload artifacts
uses: actions/upload-artifact@v4
if: always()
Expand All @@ -136,7 +135,7 @@ jobs:
e2e/artifacts/playwright/
!e2e/artifacts/playwright/*.webm
retention-days: 5

test-argentX:
runs-on: ubuntu-latest
container:
Expand Down Expand Up @@ -177,7 +176,7 @@ jobs:
with:
path: ./*
key: ${{ github.sha }}

- name: Install libarchive-tools
shell: bash
run: |
Expand All @@ -188,8 +187,8 @@ jobs:
dpkg --configure -a
apt-get update && apt-get install -y libarchive-tools
}
for i in {1..3}; do

for i in $(seq 1 3); do
echo "Attempt $i to install libarchive-tools"
if try_apt; then
echo "Successfully installed libarchive-tools"
Expand All @@ -206,7 +205,7 @@ jobs:
run: |
pnpm run start & # Start the server in background
echo "Waiting for server to be ready..."
for i in {1..30}; do
for i in $(seq 1 30); do
if curl -s http://localhost:3000 > /dev/null; then
echo "Server is ready!"
break
Expand All @@ -219,7 +218,7 @@ jobs:
sleep 1
done
xvfb-run --auto-servernum pnpm test:argentx

- name: Upload artifacts
uses: actions/upload-artifact@v4
if: always()
Expand All @@ -229,4 +228,3 @@ jobs:
e2e/artifacts/playwright/
!e2e/artifacts/playwright/*.webm
retention-days: 5

2 changes: 1 addition & 1 deletion e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const playwrightConfig: PlaywrightTestConfig = {
outputDir: config.artifactsDir,
},
],
workers: config.isCI ? 2 : 1,
workers: 1,
fullyParallel: true,
reportSlowTests: {
threshold: 2 * 60e3, // 2 minutes
Expand Down
127 changes: 85 additions & 42 deletions e2e/src/shared/src/SapoEmailClient.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import * as ImapClient from 'imap-simple';
import { simpleParser } from 'mailparser';

// Define Connection type based on what imap-simple returns
type Connection = ReturnType<typeof ImapClient.connect> extends Promise<infer T> ? T : never;

interface EmailConfig {
imap: {
user: string;
password: string;
host: string;
port: number;
tls: boolean;
tlsOptions: { rejectUnauthorized: boolean };
authTimeout: number;
};
}

export default class SapoEmailClient {
private config: any;
private readonly config: EmailConfig;
private static readonly TRASH_FOLDER = 'Lixo';
private static readonly CHECK_INTERVAL = 3000;
private static readonly EMAIL_AGE_THRESHOLD = 20000;

constructor(email: string, password: string) {
this.config = {
Expand All @@ -18,68 +36,93 @@ export default class SapoEmailClient {
};
}

private async getConnection() {
return await ImapClient.connect(this.config);
private async getConnection(): Promise<Connection> {
try {
return await ImapClient.connect(this.config);
} catch (error) {
throw new Error(`Failed to connect to IMAP server: ${error.message}`);
}
}

private async moveToTrash(connection: any, messageId: number) {
await connection.moveMessage(messageId, 'Lixo');
private async moveToTrash(connection: Connection, uid: number): Promise<void> {
try {
await connection.moveMessage(uid, SapoEmailClient.TRASH_FOLDER);
console.log(`Successfully moved message ${uid} to trash`);
} catch (error) {
console.error(`Failed to move message ${uid} to trash:`, error);
// Attempt to copy and then delete as a fallback
try {
// await connection.copy(uid, SapoEmailClient.TRASH_FOLDER);
await connection.addFlags(uid, '\\Deleted');
await connection.deleteMessage(uid);
console.log(`Successfully copied and deleted message ${uid} as fallback`);
} catch (fallbackError) {
throw new Error(`Failed to move message to trash (both methods): ${fallbackError.message}`);
}
}
}

private async processMessage(message: any): Promise<string | null> {
const body = message.parts.find(part => part.which === '');
if (!body) return null;

const parsed = await simpleParser(body.body);
const messageDate = parsed.date || new Date();

if (messageDate > new Date(Date.now() - SapoEmailClient.EMAIL_AGE_THRESHOLD)) {
return parsed.subject?.match(/\d{6}/)?.[0] || null;
}
return null;
}

async waitForEmail(timeout: number = 30000): Promise<string> {
async waitForEmail(timeout: number = 40000): Promise<string> {
const startTime = Date.now();
console.log('Waiting for verification email...');
const connection = await this.getConnection();
try {
let i = 0
while (Date.now() - startTime < timeout) {

while (Date.now() - startTime < timeout) {
try {
const connection = await this.getConnection();
await connection.openBox('INBOX');
console.log('Checking for new messages...', i++);
try {
const messages = await connection.search(['UNSEEN'], {
bodies: ['HEADER', ''],
markSeen: true
});

const messages = await connection.search(['UNSEEN'], {
bodies: ['HEADER', ''], // Include headers for date checking
markSeen: true
});

if (messages.length > 0) {
// Filter messages by received date
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
const body = message.parts.find(part => part.which === '');

if (body) {
const parsed = await simpleParser(body.body);
const messageDate = parsed.date || new Date();

// Check if message is less than 10 seconds old
if (messageDate > new Date(Date.now() - 10000)) {
const pin = parsed.subject?.match(/\d{6}/)?.[0];
if (pin) {
await this.moveToTrash(connection, message.attributes.uid);
await connection.end();
return pin;
}
}
for (const message of messages.reverse()) {
const pin = await this.processMessage(message);

if (pin) {
await connection.addFlags(message.attributes.uid, '\\Seen');
await this.moveToTrash(connection, message.attributes.uid);
return pin;
} else {
// Move old messages to trash
await this.moveToTrash(connection, message.attributes.uid);
}
}
}

await connection.end();
await new Promise(resolve => setTimeout(resolve, 2000));

} catch (error) {
console.error('Error checking email:', error);
await new Promise(resolve => setTimeout(resolve, 2000));
await new Promise(resolve => setTimeout(resolve, SapoEmailClient.CHECK_INTERVAL));
} catch (error) {
console.error('Error processing messages:', error);
await new Promise(resolve => setTimeout(resolve, SapoEmailClient.CHECK_INTERVAL));
}
}
}

throw new Error(`No verification code found within ${timeout}ms`);
throw new Error(`No verification code found within ${timeout}ms`);
} finally {
await connection.end();
}
}

async getPin(): Promise<string> {
const pin = await this.waitForEmail();
if (!pin) {
throw new Error('No verification code found in email');
}
console.log(`Found verification code: ${pin}`);
return pin;
}
}
10 changes: 7 additions & 3 deletions e2e/src/webwallet/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ const config = {
password: process.env.WW_LOGIN_PASSWORD!,
},
emailPassword: process.env.EMAIL_PASSWORD!,
acc_destination: commonConfig.destinationAddress! || '',
vw_acc_addr: process.env.VW_ACC_ADDR! || '',
acc_destination: commonConfig.destinationAddress! || "",
vw_acc_addr: process.env.VW_ACC_ADDR! || "",
url: "https://web.argent.xyz",
...commonConfig,
/*
TODO: wait for sepolia in prod
process.env.ARGENT_X_ENVIRONMENT === "prod"
? "https://web.argent.xyz" :
"" */ ...commonConfig,
}

// check that no value of config is undefined, otherwise throw error
Expand Down
Loading
Loading