issues/353/user_tool.py (131 lines of code) (raw):

import sys, os base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) sys.path.insert(0, base_path) import secrets import string import time from email.header import decode_header import base64 import pyotp import pyperclip import pytest from playwright.async_api import Page, TimeoutError, async_playwright, expect from utils.trace_cleaner import trace_cleaner USER_EMAIL = "expensifysam@gmail.com" PASSWORD = "" SLOW_MO: float | None = 500 NOW = int(time.time()) def generate_random_string(length=12): random_bytes = secrets.token_bytes(length) base64_string = base64.b64encode(random_bytes).decode("utf-8") alphanumeric_string = "".join( filter(lambda x: x in string.ascii_letters + string.digits, base64_string) ) return alphanumeric_string[:length] async def create_user(page: Page, firstname: str, lastname: str): await page.get_by_role("button", name="Join").click() await page.get_by_text("Track and budget expenses").click() await page.get_by_role("textbox", name="First name").fill(firstname) await page.get_by_role("textbox", name="Last name").fill(lastname) await page.get_by_role("button", name="Continue").click() async def login(page: Page, user_email: str, password: str): magic_code = "123456" await page.get_by_role("textbox").fill(magic_code) async def login_or_create_user(page: Page, user_email: str, password: str): await page.get_by_test_id("username").fill(user_email) await page.get_by_role("button", name="Continue").click() try: await page.get_by_role("button", name="Join").wait_for( state="visible", timeout=2000 ) firstname, lastname = generate_random_string(6), generate_random_string(6) await create_user(page, firstname, lastname) except TimeoutError: await login(page, user_email, password) async def verify_email(page: Page): await page.get_by_label("My settings").click() await page.get_by_role("menuitem", name="Profile").click() await page.get_by_text("Contact method").click() await page.get_by_test_id("ContactMethodsPage").get_by_text(USER_EMAIL).click() if await page.get_by_text("Please enter the magic code sent to").is_visible(): magic_code = "123456" await page.get_by_role("textbox").fill(magic_code) await page.get_by_role("button", name="Verify").click() await page.get_by_text("Add more ways for people to").wait_for(state="visible") await page.get_by_label("Back").first.click() # Go back to settings page else: await page.get_by_test_id("ContactMethodDetailsPage").get_by_label( "Back" ).click() await page.get_by_label("Back").first.click() # Go back to settings page async def enable_two_factor_authentication(page: Page): await page.get_by_label("Security").click() await page.get_by_label("Two-factor authentication").click() await page.get_by_text("Copy").click() # copy recovery codes with open("recovery_codes.txt", "w", encoding="utf-8") as f: f.write(pyperclip.paste()) await page.get_by_role("button", name="Next").click() await page.get_by_text("Or add this secret key to").wait_for( state="visible", timeout=2000 ) await page.get_by_text("Copy").click() secret_key = pyperclip.paste() totp = pyotp.TOTP(secret_key) otp = totp.now() await page.get_by_role("textbox").fill(otp) await page.get_by_text("Two-factor authentication is").wait_for( state="visible", timeout=2000 ) await page.get_by_role("button", name="Got it").click() await page.get_by_label("Back").click() # Go back to settings page async def enter_incorrect_otp(page: Page): await page.get_by_text("Please enter your authenticator code").wait_for( state="visible", timeout=2000 ) await page.get_by_role("textbox").fill("123456") # enter incorrect otp await expect( page.get_by_text("Please enter a valid authenticator code") ).to_be_visible() await page.get_by_label("Use recovery code").click() await page.get_by_role("textbox", name="Recovery code").fill("werwer") await page.get_by_role("textbox", name="Recovery code").press("Enter") await expect(page.get_by_text("Incorrect recovery code.")).to_be_visible() async def sign_in_with_incorrect_otp(page: Page): now = int(time.time()) - 1000 # 1 second ago await page.get_by_test_id("username").fill(USER_EMAIL) await page.get_by_role("button", name="Continue").click() magic_code = "654321" await page.get_by_role("textbox").fill(magic_code) await enter_incorrect_otp(page) @pytest.mark.asyncio async def test_recovery_code_login(): async with async_playwright() as p: browser = await p.chromium.launch(headless=True, slow_mo=SLOW_MO, args=[ "--disable-web-security", "--disable-features=IsolateOrigins,site-per-process" ]) context = await browser.new_context() await context.tracing.start(screenshots=True, snapshots=True) page = await context.new_page() try: await page.goto("https://dev.new.expensify.com:8082/") await login_or_create_user(page, USER_EMAIL, PASSWORD) try: await page.get_by_text("Please enter your authenticator code").wait_for( state="visible", timeout=2000 ) await enter_incorrect_otp(page) return except TimeoutError: pass await verify_email(page) await enable_two_factor_authentication(page) await page.get_by_label("Sign out").click() await sign_in_with_incorrect_otp(page) except Exception as e: raise e finally: await context.tracing.stop(path="/app/expensify/user_tool/output_browser1.zip") trace_cleaner("/app/expensify/user_tool/output_browser1.zip") await context.close() await browser.close()