from pathlib import Path import random import re import sys import pytest from playwright.sync_api import TimeoutError as PlaywrightTimeoutError from playwright.sync_api import expect PROJECT_ROOT = Path(__file__).resolve().parents[1] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) from config.settings import settings from tests.test_rewards_shopping_cart_login_logout import _accept_cookie_if_present from utils.logger import get_logger logger = get_logger(__name__) REDEEM_NOW_SELECTORS = [ 'button:has-text("Redeem Now"):not([disabled])', 'button:has-text("Redeem now"):not([disabled])', 'button:has-text("立即兑换"):not([disabled])', ] POINTS_PAY_SELECTORS = [ 'button:has-text("Pay"):not([disabled])', 'button:has-text("Confirm"):not([disabled])', 'button:has-text("Submit"):not([disabled])', 'button:has-text("Place Order"):not([disabled])', 'button:has-text("Redeem"):not([disabled])', 'button:has-text("Confirm Redemption"):not([disabled])', 'button:has-text("确认"):not([disabled])', 'button:has-text("提交"):not([disabled])', 'button:has-text("支付"):not([disabled])', ] def _visible_locator(page, selectors, timeout=5_000): for selector in selectors: locator = page.locator(selector).first try: if locator.is_visible(timeout=timeout): return locator except PlaywrightTimeoutError: continue raise AssertionError(f"未找到可见元素: {selectors}") def _login_in_open_dialog(page): logger.info("在 Redeem Now 触发的登录弹窗中登录") login_inputs = page.locator('input[type="text"]') expect(login_inputs.first).to_be_visible(timeout=settings.default_timeout) login_inputs.nth(0).fill(settings.login_email) login_inputs.nth(1).fill(settings.verification_code) confirmation_items = page.locator("div.inline-flex.cursor-pointer") expect(confirmation_items.nth(0)).to_be_visible(timeout=settings.default_timeout) expect(confirmation_items.nth(1)).to_be_visible(timeout=settings.default_timeout) confirmation_items.nth(0).click() confirmation_items.nth(1).click() submit_button = page.locator("button").last expect(submit_button).to_be_enabled(timeout=settings.default_timeout) submit_button.click() logger.info("等待登录弹窗关闭或兑换流程继续") expect(login_inputs.first).not_to_be_visible(timeout=settings.default_timeout) def _open_points_redemption_from_rewards(page): logger.info("未登录用户点击 Rewards 并进入 Points Redemption") rewards_link = page.locator('header a[href^="javascript:"]').filter(has_text="Rewards").first expect(rewards_link).to_be_attached(timeout=settings.default_timeout) rewards_link.click(force=True) try: page.wait_for_function( """ () => Array.from(document.querySelectorAll('a[href="/points-redemption"]')) .some(element => !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length)) """, timeout=5_000, ) page.locator('a[href="/points-redemption"]').filter(has_text="Points Redemption").first.click() except PlaywrightTimeoutError: logger.info("Rewards 下拉入口未稳定展示,直接进入 Points Redemption 页面") page.goto(settings.base_url.rstrip("/") + "/points-redemption", wait_until="domcontentloaded") expect(page).to_have_url(re.compile(r".*/points-redemption/?"), timeout=settings.default_timeout) expect(page.locator("body")).to_be_visible(timeout=settings.default_timeout) def _open_random_redeemable_points_product(page): logger.info("随机选择一个积分商品并查找 Redeem Now") category_buttons = page.locator("section button").filter(has_not_text=re.compile(r"Accepet|Accept", re.I)) expect(category_buttons.first).to_be_visible(timeout=settings.default_timeout) category_indices = list(range(category_buttons.count())) random.shuffle(category_indices) last_error = None for category_index in category_indices: try: logger.info("尝试积分商品分类/商品入口索引: %s", category_index) category_buttons.nth(category_index).click() page.wait_for_timeout(1_500) if page.get_by_text(re.compile(r"currently do not support delivery", re.I)).first.is_visible(timeout=1_000): last_error = "当前国家/地区不支持积分商品配送" continue redeem_button = _visible_locator(page, REDEEM_NOW_SELECTORS, timeout=5_000) expect(redeem_button).to_be_enabled(timeout=settings.default_timeout) redeem_button.click() return except (AssertionError, PlaywrightTimeoutError) as exc: last_error = exc logger.info("当前积分商品不可兑换,继续尝试下一个: %s", exc) pytest.skip(f"当前测试环境未展示可兑换积分商品,无法执行 Redeem Now 主流程: {last_error}") def _complete_points_payment_after_login(page): logger.info("登录后继续完成积分商品支付/兑换") try: pay_button = _visible_locator(page, POINTS_PAY_SELECTORS, timeout=settings.default_timeout) expect(pay_button).to_be_enabled(timeout=settings.default_timeout) pay_button.click() except AssertionError: logger.info("登录后未出现独立确认按钮,检查是否已自动提交兑换") result_text = page.get_by_text( re.compile(r"success|successful|completed|paid|payment|order|redeem|redemption|成功|订单|兑换", re.I) ).first expect(result_text).to_be_visible(timeout=settings.default_timeout) def test_guest_points_product_redeem_now_login_then_points_payment_result(page, ensure_guest_user): logger.info("未登录用户从 Rewards 选择积分商品,Redeem Now 后登录并完成积分支付") page.goto(settings.base_url, wait_until="domcontentloaded") ensure_guest_user() _open_points_redemption_from_rewards(page) _open_random_redeemable_points_product(page) logger.info("校验 Redeem Now 后弹出登录弹窗") expect(page.locator('input[type="text"]').first).to_be_visible(timeout=settings.default_timeout) _login_in_open_dialog(page) _complete_points_payment_after_login(page) if __name__ == "__main__": raise SystemExit(pytest.main([str(Path(__file__))]))