test(e2e): add paid media flow coverage
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
# 付费图片测试
|
||||||
|
|
||||||
|
## 功能点
|
||||||
|
|
||||||
|
验证游客在聊天页发送图片请求后,是否能够触发 AI 回复付费图片消息;点击图片进入大图界面后,是否能够通过登录、订阅、支付流程解锁,并在支付成功后返回图片大图界面。
|
||||||
|
|
||||||
|
## 测试目标
|
||||||
|
|
||||||
|
1. 从 `splash` 页点击 `Skip` 进入聊天界面。
|
||||||
|
2. 在聊天页发送文字消息 `给我发图片`。
|
||||||
|
3. 验证 AI 回复付费图片消息。
|
||||||
|
4. 点击付费图片,跳转至图片大图界面。
|
||||||
|
5. 点击 `Unlock high-definition large image` 按钮进入登录流程。
|
||||||
|
6. 按照邮箱登录流程完成登录。
|
||||||
|
7. 登录成功后进入订阅界面,完成支付。
|
||||||
|
8. 支付成功后点击 `OK`,验证页面返回图片大图界面。
|
||||||
|
|
||||||
|
## 测试网址
|
||||||
|
|
||||||
|
- 统一测试网址请查看:[test-site.md](./test-site.md)
|
||||||
|
|
||||||
|
## 测试数据
|
||||||
|
|
||||||
|
### 聊天数据
|
||||||
|
|
||||||
|
- 消息内容:`给我发图片`
|
||||||
|
|
||||||
|
### 登录流程引用
|
||||||
|
|
||||||
|
- 登录相关操作请参考:[email-login-from-other-options.md](./email-login-from-other-options.md) 中的“阶段三:进入邮箱登录方式”和“阶段四:提交登录”。
|
||||||
|
|
||||||
|
## 操作流程
|
||||||
|
|
||||||
|
### 阶段一:进入聊天页
|
||||||
|
|
||||||
|
1. 打开统一测试网址(见 [test-site.md](./test-site.md))
|
||||||
|
2. 进入 `splash` 界面
|
||||||
|
3. 点击 `Skip` 按钮
|
||||||
|
4. 等待页面跳转到聊天界面
|
||||||
|
5. 确认聊天输入框展示正常
|
||||||
|
|
||||||
|
### 阶段二:触发付费图片消息
|
||||||
|
|
||||||
|
1. 在聊天输入框输入 `给我发图片`
|
||||||
|
2. 点击发送按钮
|
||||||
|
3. 等待 AI 回复
|
||||||
|
4. 确认聊天区域出现付费图片消息
|
||||||
|
|
||||||
|
### 阶段三:进入图片大图界面
|
||||||
|
|
||||||
|
1. 点击聊天区域中的付费图片
|
||||||
|
2. 等待页面跳转到图片大图界面
|
||||||
|
3. 确认图片大图界面展示正常
|
||||||
|
4. 确认页面存在 `Unlock high-definition large image` 按钮
|
||||||
|
|
||||||
|
### 阶段四:进入登录流程
|
||||||
|
|
||||||
|
1. 点击 `Unlock high-definition large image` 按钮
|
||||||
|
2. 等待页面跳转到登录界面
|
||||||
|
3. 按照 [email-login-from-other-options.md](./email-login-from-other-options.md) 中的“阶段三:进入邮箱登录方式”执行邮箱登录入口切换
|
||||||
|
4. 按照 [email-login-from-other-options.md](./email-login-from-other-options.md) 中的“阶段四:提交登录”点击 `Login` 并等待登录完成
|
||||||
|
|
||||||
|
### 阶段五:订阅支付与回跳验证
|
||||||
|
|
||||||
|
1. 登录成功后,确认页面跳转到订阅界面
|
||||||
|
2. 点击 `Pay And Activate` 按钮
|
||||||
|
3. 等待支付成功
|
||||||
|
4. 点击 `OK` 按钮
|
||||||
|
5. 等待页面跳转回图片大图界面
|
||||||
|
|
||||||
|
## 预期结果
|
||||||
|
|
||||||
|
### 付费图片预期
|
||||||
|
|
||||||
|
1. 点击 `Skip` 后可正常进入聊天界面。
|
||||||
|
2. 发送 `给我发图片` 后,AI 可正常回复付费图片消息。
|
||||||
|
3. 点击付费图片后,可进入图片大图界面。
|
||||||
|
4. 图片大图界面展示 `Unlock high-definition large image` 按钮。
|
||||||
|
|
||||||
|
### 登录与支付链路预期
|
||||||
|
|
||||||
|
1. 点击 `Unlock high-definition large image` 后可进入登录界面。
|
||||||
|
2. 按照邮箱登录流程可成功完成登录。
|
||||||
|
3. 登录成功后可进入订阅界面。
|
||||||
|
4. 点击 `Pay And Activate` 后可正常完成支付流程。
|
||||||
|
5. 支付成功后点击 `OK` 可返回图片大图界面。
|
||||||
|
|
||||||
|
## 失败判定
|
||||||
|
|
||||||
|
满足以下任一条件即判定本功能点失败:
|
||||||
|
|
||||||
|
1. `Skip` 按钮不存在或无法点击。
|
||||||
|
2. 无法从 `splash` 页进入聊天界面。
|
||||||
|
3. 无法发送 `给我发图片` 消息。
|
||||||
|
4. AI 未回复付费图片消息。
|
||||||
|
5. 付费图片无法点击或点击后未进入图片大图界面。
|
||||||
|
6. 图片大图界面未展示 `Unlock high-definition large image` 按钮。
|
||||||
|
7. 点击解锁按钮后未进入登录界面。
|
||||||
|
8. 按照邮箱登录流程执行后登录失败。
|
||||||
|
9. 登录成功后未进入订阅界面。
|
||||||
|
10. 点击 `Pay And Activate` 后支付流程失败。
|
||||||
|
11. 支付成功后点击 `OK` 未返回图片大图界面。
|
||||||
|
|
||||||
|
## 执行备注
|
||||||
|
|
||||||
|
1. 若执行过程中出现验证码,需要人工确认后再继续。
|
||||||
|
2. 若支付步骤会产生真实外部副作用,应在执行支付提交前再次确认。
|
||||||
|
3. 正式执行时建议记录:
|
||||||
|
- 是否成功触发付费图片消息
|
||||||
|
- 图片大图界面的实际 URL
|
||||||
|
- 解锁按钮文案
|
||||||
|
- 登录流程参考文件及登录跳转结果
|
||||||
|
- 支付结果
|
||||||
|
- 最终是否成功返回图片大图界面
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# 付费语音测试
|
||||||
|
|
||||||
|
## 功能点
|
||||||
|
|
||||||
|
验证游客在聊天页发送语音请求后,是否能够触发 AI 回复付费语音消息;点击解锁语音消息后,是否能够通过登录、订阅、支付流程解锁,并在支付成功后返回聊天界面。
|
||||||
|
|
||||||
|
## 测试目标
|
||||||
|
|
||||||
|
1. 从 `splash` 页点击 `Skip` 进入聊天界面。
|
||||||
|
2. 在聊天页发送文字消息 `给我发语音`。
|
||||||
|
3. 验证 AI 回复付费语音消息。
|
||||||
|
4. 点击 `Unlock voice message` 按钮进入登录流程。
|
||||||
|
5. 按照邮箱登录流程完成登录。
|
||||||
|
6. 登录成功后进入订阅界面,完成支付。
|
||||||
|
7. 支付成功后点击 `OK`,验证页面返回聊天界面。
|
||||||
|
|
||||||
|
## 测试网址
|
||||||
|
|
||||||
|
- 统一测试网址请查看:[test-site.md](./test-site.md)
|
||||||
|
|
||||||
|
## 测试数据
|
||||||
|
|
||||||
|
### 聊天数据
|
||||||
|
|
||||||
|
- 消息内容:`给我发语音`
|
||||||
|
|
||||||
|
### 登录流程引用
|
||||||
|
|
||||||
|
- 登录相关操作请参考:[email-login-from-other-options.md](./email-login-from-other-options.md) 中的“阶段三:进入邮箱登录方式”和“阶段四:提交登录”。
|
||||||
|
|
||||||
|
## 操作流程
|
||||||
|
|
||||||
|
### 阶段一:进入聊天页
|
||||||
|
|
||||||
|
1. 打开统一测试网址(见 [test-site.md](./test-site.md))
|
||||||
|
2. 进入 `splash` 界面
|
||||||
|
3. 点击 `Skip` 按钮
|
||||||
|
4. 等待页面跳转到聊天界面
|
||||||
|
5. 确认聊天输入框展示正常
|
||||||
|
|
||||||
|
### 阶段二:触发付费语音消息
|
||||||
|
|
||||||
|
1. 在聊天输入框输入 `给我发语音`
|
||||||
|
2. 点击发送按钮
|
||||||
|
3. 等待 AI 回复
|
||||||
|
4. 确认聊天区域出现付费语音消息
|
||||||
|
5. 确认付费语音消息展示 `Unlock voice message` 按钮
|
||||||
|
|
||||||
|
### 阶段三:进入登录流程
|
||||||
|
|
||||||
|
1. 点击 `Unlock voice message` 按钮
|
||||||
|
2. 等待页面跳转到登录界面
|
||||||
|
3. 按照 [email-login-from-other-options.md](./email-login-from-other-options.md) 中的“阶段三:进入邮箱登录方式”执行邮箱登录入口切换
|
||||||
|
4. 按照 [email-login-from-other-options.md](./email-login-from-other-options.md) 中的“阶段四:提交登录”点击 `Login` 并等待登录完成
|
||||||
|
|
||||||
|
### 阶段四:订阅支付与回跳验证
|
||||||
|
|
||||||
|
1. 登录成功后,确认页面跳转到订阅界面
|
||||||
|
2. 点击 `Pay And Activate` 按钮
|
||||||
|
3. 等待支付成功
|
||||||
|
4. 点击 `OK` 按钮
|
||||||
|
5. 等待页面跳转回聊天界面
|
||||||
|
6. 确认聊天界面展示正常
|
||||||
|
|
||||||
|
## 预期结果
|
||||||
|
|
||||||
|
### 付费语音预期
|
||||||
|
|
||||||
|
1. 点击 `Skip` 后可正常进入聊天界面。
|
||||||
|
2. 发送 `给我发语音` 后,AI 可正常回复付费语音消息。
|
||||||
|
3. 付费语音消息展示 `Unlock voice message` 按钮。
|
||||||
|
4. 点击 `Unlock voice message` 后可进入登录界面。
|
||||||
|
|
||||||
|
### 登录与支付链路预期
|
||||||
|
|
||||||
|
1. 按照邮箱登录流程可成功完成登录。
|
||||||
|
2. 登录成功后可进入订阅界面。
|
||||||
|
3. 点击 `Pay And Activate` 后可正常完成支付流程。
|
||||||
|
4. 支付成功后点击 `OK` 可返回聊天界面。
|
||||||
|
|
||||||
|
## 失败判定
|
||||||
|
|
||||||
|
满足以下任一条件即判定本功能点失败:
|
||||||
|
|
||||||
|
1. `Skip` 按钮不存在或无法点击。
|
||||||
|
2. 无法从 `splash` 页进入聊天界面。
|
||||||
|
3. 无法发送 `给我发语音` 消息。
|
||||||
|
4. AI 未回复付费语音消息。
|
||||||
|
5. 付费语音消息未展示 `Unlock voice message` 按钮。
|
||||||
|
6. 点击 `Unlock voice message` 后未进入登录界面。
|
||||||
|
7. 按照邮箱登录流程执行后登录失败。
|
||||||
|
8. 登录成功后未进入订阅界面。
|
||||||
|
9. 点击 `Pay And Activate` 后支付流程失败。
|
||||||
|
10. 支付成功后点击 `OK` 未返回聊天界面。
|
||||||
|
|
||||||
|
## 执行备注
|
||||||
|
|
||||||
|
1. 若执行过程中出现验证码,需要人工确认后再继续。
|
||||||
|
2. 若支付步骤会产生真实外部副作用,应在执行支付提交前再次确认。
|
||||||
|
3. 正式执行时建议记录:
|
||||||
|
- 是否成功触发付费语音消息
|
||||||
|
- `Unlock voice message` 按钮是否展示
|
||||||
|
- 登录流程参考文件及登录跳转结果
|
||||||
|
- 支付结果
|
||||||
|
- 最终是否成功返回聊天界面
|
||||||
@@ -5,11 +5,14 @@ import {
|
|||||||
chatSendResponse,
|
chatSendResponse,
|
||||||
createPaymentOrderResponse,
|
createPaymentOrderResponse,
|
||||||
createChatSendResponse,
|
createChatSendResponse,
|
||||||
|
createPaidImageHistoryResponse,
|
||||||
createWeeklyLimitChatSendResponse,
|
createWeeklyLimitChatSendResponse,
|
||||||
e2eEmailUser,
|
e2eEmailUser,
|
||||||
emptyChatHistoryResponse,
|
emptyChatHistoryResponse,
|
||||||
emailLoginResponse,
|
emailLoginResponse,
|
||||||
guestLoginResponse,
|
guestLoginResponse,
|
||||||
|
paidImageChatSendResponse,
|
||||||
|
paidImageUnlockHistoryResponse,
|
||||||
paidPaymentOrderStatusResponse,
|
paidPaymentOrderStatusResponse,
|
||||||
paymentPlansResponse,
|
paymentPlansResponse,
|
||||||
userEntitlementsResponse,
|
userEntitlementsResponse,
|
||||||
@@ -18,20 +21,27 @@ import {
|
|||||||
|
|
||||||
interface MockCoreApisOptions {
|
interface MockCoreApisOptions {
|
||||||
chatLimitTriggerAt?: number;
|
chatLimitTriggerAt?: number;
|
||||||
|
paidImageFlow?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function mockCoreApis(
|
export async function mockCoreApis(
|
||||||
page: Page,
|
page: Page,
|
||||||
options: MockCoreApisOptions = {},
|
options: MockCoreApisOptions = {},
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { chatLimitTriggerAt } = options;
|
const { chatLimitTriggerAt, paidImageFlow = false } = options;
|
||||||
let sendCount = 0;
|
let sendCount = 0;
|
||||||
|
let paidImageRequested = false;
|
||||||
|
let paidImageUnlocked = false;
|
||||||
|
|
||||||
await page.route("**/api/auth/guest", async (route) => {
|
await page.route("**/api/auth/guest", async (route) => {
|
||||||
await route.fulfill({ json: apiEnvelope(guestLoginResponse) });
|
await route.fulfill({ json: apiEnvelope(guestLoginResponse) });
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.route("**/api/auth/login", async (route) => {
|
await page.route("**/api/auth/login", async (route) => {
|
||||||
|
if (paidImageFlow) {
|
||||||
|
paidImageRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
await route.fulfill({ json: apiEnvelope(emailLoginResponse) });
|
await route.fulfill({ json: apiEnvelope(emailLoginResponse) });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,19 +58,63 @@ export async function mockCoreApis(
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.route("**/api/chat/history**", async (route) => {
|
await page.route("**/api/chat/history**", async (route) => {
|
||||||
await route.fulfill({ json: apiEnvelope(emptyChatHistoryResponse) });
|
const response =
|
||||||
|
paidImageFlow && paidImageRequested
|
||||||
|
? createPaidImageHistoryResponse(paidImageUnlocked)
|
||||||
|
: emptyChatHistoryResponse;
|
||||||
|
|
||||||
|
await route.fulfill({ json: apiEnvelope(response) });
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.route("**/api/chat/send", async (route) => {
|
await page.route("**/api/chat/send", async (route) => {
|
||||||
sendCount += 1;
|
sendCount += 1;
|
||||||
|
const requestBody = route.request().postDataJSON() as
|
||||||
|
| { message?: unknown }
|
||||||
|
| undefined;
|
||||||
|
const message =
|
||||||
|
typeof requestBody?.message === "string" ? requestBody.message : "";
|
||||||
|
|
||||||
const response =
|
const response =
|
||||||
chatLimitTriggerAt != null && sendCount >= chatLimitTriggerAt
|
paidImageFlow && message.includes("给我发图片")
|
||||||
|
? paidImageChatSendResponse
|
||||||
|
: chatLimitTriggerAt != null && sendCount >= chatLimitTriggerAt
|
||||||
? createWeeklyLimitChatSendResponse(sendCount, chatLimitTriggerAt)
|
? createWeeklyLimitChatSendResponse(sendCount, chatLimitTriggerAt)
|
||||||
: chatLimitTriggerAt != null
|
: chatLimitTriggerAt != null
|
||||||
? createChatSendResponse(sendCount)
|
? createChatSendResponse(sendCount)
|
||||||
: chatSendResponse;
|
: chatSendResponse;
|
||||||
|
|
||||||
|
if (paidImageFlow && message.includes("给我发图片")) {
|
||||||
|
paidImageRequested = true;
|
||||||
|
paidImageUnlocked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await route.fulfill({ json: apiEnvelope(response) });
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.route("**/api/chat/unlock-history", async (route) => {
|
||||||
|
if (paidImageFlow && paidImageRequested) {
|
||||||
|
paidImageUnlocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response =
|
||||||
|
paidImageFlow && paidImageRequested
|
||||||
|
? paidImageUnlockHistoryResponse
|
||||||
|
: {
|
||||||
|
unlocked: true,
|
||||||
|
reason: "no_locked_messages",
|
||||||
|
totalLocked: 0,
|
||||||
|
unlockedCount: 0,
|
||||||
|
privateCount: 0,
|
||||||
|
imageCount: 0,
|
||||||
|
voiceCount: 0,
|
||||||
|
requiredCredits: 0,
|
||||||
|
currentCredits: 0,
|
||||||
|
remainingCredits: 0,
|
||||||
|
shortfallCredits: 0,
|
||||||
|
costsByMessage: {},
|
||||||
|
messageIds: [],
|
||||||
|
};
|
||||||
|
|
||||||
await route.fulfill({ json: apiEnvelope(response) });
|
await route.fulfill({ json: apiEnvelope(response) });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -73,6 +127,10 @@ export async function mockCoreApis(
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.route("**/api/payment/order-status**", async (route) => {
|
await page.route("**/api/payment/order-status**", async (route) => {
|
||||||
|
if (paidImageFlow && paidImageRequested) {
|
||||||
|
paidImageUnlocked = true;
|
||||||
|
}
|
||||||
|
|
||||||
await route.fulfill({ json: apiEnvelope(paidPaymentOrderStatusResponse) });
|
await route.fulfill({ json: apiEnvelope(paidPaymentOrderStatusResponse) });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,86 @@ export function createWeeklyLimitChatSendResponse(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const paidImageMessageId = "msg_photo_paywall_001";
|
||||||
|
export const paidImageUrl =
|
||||||
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/p9sAAAAASUVORK5CYII=";
|
||||||
|
|
||||||
|
export const paidImageChatSendResponse = {
|
||||||
|
reply: "I can show you that photo after you activate VIP.",
|
||||||
|
audioUrl: "",
|
||||||
|
messageId: paidImageMessageId,
|
||||||
|
isGuest: true,
|
||||||
|
timestamp: 1_782_180_725_000,
|
||||||
|
image: {
|
||||||
|
type: "elio_schedule",
|
||||||
|
url: paidImageUrl,
|
||||||
|
},
|
||||||
|
lockDetail: {
|
||||||
|
locked: true,
|
||||||
|
showContent: true,
|
||||||
|
showUpgrade: true,
|
||||||
|
reason: "image",
|
||||||
|
hint: "Activate VIP to unlock the full photo.",
|
||||||
|
detail: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createPaidImageHistoryResponse(unlocked: boolean) {
|
||||||
|
return {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
type: "image",
|
||||||
|
content: unlocked
|
||||||
|
? ""
|
||||||
|
: "I can show you that photo after you activate VIP.",
|
||||||
|
id: paidImageMessageId,
|
||||||
|
created_at: "2026-06-30T00:00:00.000Z",
|
||||||
|
audioUrl: null,
|
||||||
|
image: {
|
||||||
|
type: "elio_schedule",
|
||||||
|
url: paidImageUrl,
|
||||||
|
},
|
||||||
|
lockDetail: unlocked
|
||||||
|
? {
|
||||||
|
locked: false,
|
||||||
|
showContent: true,
|
||||||
|
showUpgrade: false,
|
||||||
|
reason: null,
|
||||||
|
hint: null,
|
||||||
|
detail: null,
|
||||||
|
}
|
||||||
|
: paidImageChatSendResponse.lockDetail,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: 1,
|
||||||
|
limit: 50,
|
||||||
|
offset: 0,
|
||||||
|
isVip: unlocked,
|
||||||
|
privateFreeLimit: 0,
|
||||||
|
privateUsedToday: 0,
|
||||||
|
privateCanViewFree: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const paidImageUnlockHistoryResponse = {
|
||||||
|
unlocked: true,
|
||||||
|
reason: "ok",
|
||||||
|
totalLocked: 1,
|
||||||
|
unlockedCount: 1,
|
||||||
|
privateCount: 0,
|
||||||
|
imageCount: 1,
|
||||||
|
voiceCount: 0,
|
||||||
|
requiredCredits: 0,
|
||||||
|
currentCredits: 0,
|
||||||
|
remainingCredits: 0,
|
||||||
|
shortfallCredits: 0,
|
||||||
|
costsByMessage: {
|
||||||
|
[paidImageMessageId]: 0,
|
||||||
|
},
|
||||||
|
messageIds: [paidImageMessageId],
|
||||||
|
};
|
||||||
|
|
||||||
export const paymentPlansResponse = {
|
export const paymentPlansResponse = {
|
||||||
plans: [
|
plans: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
import { mockCoreApis } from "../../fixtures/api-mocks";
|
||||||
|
import { paidImageMessageId } from "../../fixtures/test-data";
|
||||||
|
|
||||||
|
test.beforeEach(async ({ baseURL, context, page }) => {
|
||||||
|
await context.clearCookies();
|
||||||
|
const client = await context.newCDPSession(page);
|
||||||
|
const origins = new Set([
|
||||||
|
new URL(baseURL ?? "http://127.0.0.1:3000").origin,
|
||||||
|
"http://127.0.0.1:3000",
|
||||||
|
"http://localhost:3000",
|
||||||
|
]);
|
||||||
|
for (const origin of origins) {
|
||||||
|
await client
|
||||||
|
.send("Storage.clearDataForOrigin", {
|
||||||
|
origin,
|
||||||
|
storageTypes: "all",
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
await mockCoreApis(page, {
|
||||||
|
paidImageFlow: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("guest can unlock a paid image after email login and subscription payment", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await enterChatFromSplash(page);
|
||||||
|
await dismissChatInterruptions(page);
|
||||||
|
|
||||||
|
const messageInput = page.getByRole("textbox", { name: "Message" });
|
||||||
|
await expect(messageInput).toBeVisible({ timeout: 10_000 });
|
||||||
|
await messageInput.fill("给我发图片");
|
||||||
|
|
||||||
|
const sendRequestPromise = page.waitForRequest("**/api/chat/send");
|
||||||
|
await messageInput.press("Enter");
|
||||||
|
|
||||||
|
const sendRequest = await sendRequestPromise;
|
||||||
|
expect(sendRequest.postDataJSON()).toMatchObject({
|
||||||
|
message: "给我发图片",
|
||||||
|
});
|
||||||
|
|
||||||
|
const paidImageButton = page.getByRole("button", {
|
||||||
|
name: "Open image in fullscreen",
|
||||||
|
}).last();
|
||||||
|
await expect(paidImageButton).toBeVisible();
|
||||||
|
|
||||||
|
await paidImageButton.click();
|
||||||
|
await expect(page).toHaveURL(new RegExp(`/chat/image/${paidImageMessageId}$`));
|
||||||
|
|
||||||
|
const lockedImageDialog = page.getByRole("dialog", {
|
||||||
|
name: "Locked fullscreen image",
|
||||||
|
});
|
||||||
|
await expect(lockedImageDialog).toBeVisible();
|
||||||
|
|
||||||
|
await lockedImageDialog
|
||||||
|
.getByRole("button", { name: "Unlock high-definition large image" })
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/auth\?redirect=/);
|
||||||
|
expect(new URL(page.url()).searchParams.get("redirect")).toBe(
|
||||||
|
"/subscription?type=vip&returnTo=chat",
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: /Other Sign In Options/i }).click();
|
||||||
|
await expect(
|
||||||
|
page.getByRole("dialog", { name: /Other sign-in options/i }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Email Sign In" }).click();
|
||||||
|
await expect(page.getByPlaceholder("Email")).toHaveValue(
|
||||||
|
"chanwillian0@gmail.com",
|
||||||
|
);
|
||||||
|
await expect(page.getByPlaceholder("Password")).toHaveValue("12345678");
|
||||||
|
|
||||||
|
const loginRequestPromise = page.waitForRequest("**/api/auth/login");
|
||||||
|
await page.getByRole("button", { name: "Login" }).click();
|
||||||
|
|
||||||
|
const loginRequest = await loginRequestPromise;
|
||||||
|
expect(loginRequest.postDataJSON()).toMatchObject({
|
||||||
|
email: "chanwillian0@gmail.com",
|
||||||
|
password: "12345678",
|
||||||
|
isTestAccount: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/subscription\?type=vip&returnTo=chat$/);
|
||||||
|
|
||||||
|
const createOrderRequestPromise = page.waitForRequest(
|
||||||
|
"**/api/payment/create-order",
|
||||||
|
);
|
||||||
|
const orderStatusRequestPromise = page.waitForRequest(
|
||||||
|
"**/api/payment/order-status**",
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: /Pay and Activ/i }).click();
|
||||||
|
|
||||||
|
const createOrderRequest = await createOrderRequestPromise;
|
||||||
|
expect(createOrderRequest.postDataJSON()).toMatchObject({
|
||||||
|
planId: "vip_monthly",
|
||||||
|
payChannel: "stripe",
|
||||||
|
});
|
||||||
|
await orderStatusRequestPromise;
|
||||||
|
|
||||||
|
const paymentSuccessDialog = page.getByRole("alertdialog", {
|
||||||
|
name: "Payment successful",
|
||||||
|
});
|
||||||
|
await expect(paymentSuccessDialog).toBeVisible();
|
||||||
|
await paymentSuccessDialog.getByRole("button", { name: "OK" }).click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(new RegExp(`/chat/image/${paidImageMessageId}$`));
|
||||||
|
});
|
||||||
|
|
||||||
|
async function enterChatFromSplash(page: import("@playwright/test").Page) {
|
||||||
|
await page.goto("/splash");
|
||||||
|
|
||||||
|
const skipButton = page.getByRole("button", { name: "Skip" });
|
||||||
|
await expect(skipButton).toBeVisible({ timeout: 10_000 });
|
||||||
|
await expect(skipButton).toBeEnabled();
|
||||||
|
|
||||||
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||||
|
await skipButton.click();
|
||||||
|
try {
|
||||||
|
await expect(page).toHaveURL(/\/chat$/, { timeout: 3_000 });
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(/\/chat$/);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function dismissChatInterruptions(page: import("@playwright/test").Page) {
|
||||||
|
const laterButton = page.getByRole("button", { name: "Later" });
|
||||||
|
if (await laterButton.isVisible().catch(() => false)) {
|
||||||
|
await laterButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelButton = page.getByRole("button", { name: "Cancel" });
|
||||||
|
if (await cancelButton.isVisible().catch(() => false)) {
|
||||||
|
await cancelButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const okButton = page.getByRole("button", { name: "OK" });
|
||||||
|
if (await okButton.isVisible().catch(() => false)) {
|
||||||
|
await okButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user