一个全面的浏览器测试解决方案,适用于Shopify嵌入式应用。本地模拟Shopify Admin环境和App Bridge API,无需真实的Shopify凭据、验证码或双因素认证。
</div>▶️ 观看演示视频 - 查看Mock Bridge如何简化Shopify应用测试
</div>测试Shopify嵌入式应用很困难:
这个包解决了所有这些问题:
npm install @getverdict/mock-bridge --save-dev
# 或
yarn add @getverdict/mock-bridge --dev
# 或
pnpm add @getverdict/mock-bridge --save-dev
# 1. 安装包
npm install @getverdict/mock-bridge --save-dev
# 2. 启动模拟(只需提供你的应用URL)
npx @getverdict/mock-bridge http://localhost:3000
就这样! 你的应用现在在模拟的Shopify Admin中运行于 http://localhost:3080
# 最简单的方法 - 只需提供你的应用URL
npx @getverdict/mock-bridge http://localhost:3000
# 自动检测来自SHOPIFY_API_KEY环境变量的客户端ID
# 自动检测常见的应用路径和配置
# 生成配置文件
npx @getverdict/mock-bridge init
# 编辑生成的mock.config.js,然后运行:
npx @getverdict/mock-bridge
# 添加到package.json脚本以便轻松访问
{
"scripts": {
"dev": "next dev",
"mock:admin": "mock-bridge http://localhost:3000",
"dev:mock": "concurrently \"npm run dev\" \"npm run mock:admin\""
}
}
如果你需要更多控制,仍然可以使用编程API:
// scripts/start-mock-admin.js
const { MockShopifyAdminServer } = require("@verdict/mock-bridge");
async function startMockAdmin() {
const server = new MockShopifyAdminServer({
appUrl: "http://localhost:3000",
clientId: process.env.SHOPIFY_API_KEY,
clientSecret: "mock-secret-12345",
port: 3080,
debug: true,
});
await server.start();
console.log("🎉 模拟的Shopify Admin已准备好 http://localhost:3080");
}
startMockAdmin().catch(console.error);
使你的应用能够检测并使用模拟环境:
替换你的App Bridge脚本加载:
<!-- 前:直接从CDN加载 -->
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
<!-- 后:智能加载,支持模拟 -->
<script>
// 检查是否处于模拟环境中
const urlParams = new URLSearchParams(window.location.search);
const isEmbedded = urlParams.get("embedded") === "1";
const host = urlParams.get("host");
let isMockEnvironment = false;
// 从URL参数检测模拟环境
if (isEmbedded && host) {
try {
const decodedHost = atob(host);
if (
decodedHost.includes("localhost") ||
decodedHost.includes("mock") ||
window.location.hostname === "localhost"
) {
isMockEnvironment = true;
}
} catch (e) {
// 忽略解码错误
}
}
// 加载适当的App Bridge
if (isMockEnvironment) {
console.log("正在加载模拟App Bridge");
const script = document.createElement("script");
script.src = "http://localhost:3080/app-bridge.js";
script.onerror = () => {
// 如果模拟失败,回退到真实的CDN
const fallback = document.createElement("script");
fallback.src = "https://cdn.shopify.com/shopifycloud/app-bridge.js";
document.head.appendChild(fallback);
};
document.head.appendChild(script);
} else {
console.log("正在加载真实的Shopify App Bridge");
const script = document.createElement("script");
script.src = "https://cdn.shopify.com/shopifycloud/app-bridge.js";
document.head.appendChild(script);
}
</script>
// app.tsx 或 _app.tsx
import { setupAppBridge } from "@getverdict/mock-bridge/client";
useEffect(() => {
setupAppBridge({
debug: true,
onMockDetected: (mockServerUrl) => {
console.log("检测到模拟环境:", mockServerUrl);
},
onShopifyDetected: () => {
console.log("检测到真实的Shopify环境");
},
})
.then(() => {
console.log("App Bridge加载成功");
})
.catch(console.error);
}, []);
使你的后端支持模拟会话令牌以及真实的令牌:
// 前:只有真实的Shopify令牌
import { verifyShopifyJWT } from "./your-auth";
export async function authenticate(token: string) {
const authData = await verifyShopifyJWT(token);
// ...其余身份验证逻辑
}
// 后:支持模拟和真实的令牌
import {
validateSessionToken,
createMockUser,
} from "@getverdict/mock-bridge/auth";
export async function authenticate(token: string) {
const authData = await validateSessionToken(token, {
shopifySecret: process.env.SHOPIFY_API_PRIVATE_KEY!,
});
if (!authData) {
throw new Error("无效的令牌");
}
// 从你的数据库中获取商店(对模拟和真实环境都相同)
const shop = await getShopByName(authData.shopName);
if (!shop) {
throw new Error("未找到商店");
}
if (authData.isMock) {
// 模拟环境 - 跳过Shopify API调用
return {
shop,
currentUser: createMockUser({
shopName: authData.shopName,
permissions: shop.settings?.defaultStaffPermissions,
}),
isMock: true,
};
} else {
// 真实环境 - 继续正常的Shopify流程
const currentUser = await exchangeTokenForUser(token, shop);
return {
shop,
currentUser,
isMock: false,
};
}
}
Next.js API路由:
// pages/api/products.ts
import { validateSessionToken } from "@getverdict/mock-bridge/auth";
export default async function handler(req, res) {
const token = req.headers.authorization?.replace("Bearer ", "");
const authData = await validateSessionToken(token, {
shopifySecret: process.env.SHOPIFY_API_PRIVATE_KEY!,
});
if (!authData) {
return res.status(401).json({ error: "未经授权" });
}
if (authData.isMock) {
// 返回用于测试的模拟数据
return res.json({
products: [
{ id: "1", title: "模拟产品1", price: "19.99" },
{ id: "2", title: "模拟产品2", price: "29.99" },
],
});
} else {
// 从Shopify获取真实的产品
const products = await fetchShopifyProducts(authData.shopName);
return res.json({ products });
}
}
Express.js中间件:
import {
validateSessionToken,
createMockUser,
} from "@getverdict/mock-bridge/auth";
function createAuthMiddleware() {
return async (req, res, next) => {
const token = req.headers.authorization?.replace("Bearer ", "");
const authData = await validateSessionToken(token, {
shopifySecret: process.env.SHOPIFY_API_PRIVATE_KEY!,
});
if (!authData) {
return res.status(401).json({ error: "未经授权" });
}
const shop = await getShopByName(authData.shopName);
req.shop = shop;
req.isMockAuth = authData.isMock;
if (authData.isMock) {
req.currentUser = createMockUser({ shopName: authData.shopName });
} else {
req.currentUser = await exchangeTokenForUser(token, shop);
}
next();
};
}
app.use("/api/*", createAuthMiddleware());
确保你的模拟商店存在于数据库中:
// 添加到你的数据库种子或设置脚本
async function setupMockShop() {
const mockShop = {
name: "test-shop.myshopify.com",
displayName: "模拟测试商店",
accessToken: "mock-access-token",
active: true,
settings: {
defaultStaffPermissions: [
"read_products",
"write_products",
"read_orders",
"write_orders",
],
},
};
await createOrUpdateShop(mockShop);
console.log("创建了用于测试的模拟商店");
}
// 在开发设置期间运行
if (process.env.NODE_ENV === "development") {
setupMockShop();
}
# 方案1:CLI命令(最简单)
npx @getverdict/mock-bridge http://localhost:3000
# 方案2:Package.json脚本
npm run dev:mock
# 方案3:分离终端
npm run dev # 终端1:你的应用
npm run mock:admin # 终端2:模拟管理后台
然后导航到:
# 基本用法,自动检测
npx @getverdict/mock-bridge http://localhost:3000
# 如果本地安装,你可以使用更短的命令:
# npm install @getverdict/mock-bridge --save-dev
# npx mock-bridge http://localhost:3000
# 完整配置
npx @getverdict/mock-bridge http://localhost:3000/shopify \
--client-id your-client-id \
--port 3080 \
--debug
# 使用配置文件
npx @getverdict/mock-bridge init # 创建配置文件
npx @getverdict/mock-bridge # 使用配置文件
# 帮助和版本
npx @getverdict/mock-bridge --help
n