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()