> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 在预发布环境中每晚运行 E2E 测试

export const UseCaseHero = ({title, description, prompt, category, features, devinUrl, agent, intent, playbookId, type}) => {
  const encodedPrompt = encodeURIComponent(prompt || '');
  const tag = 'docs-use-case-gallery';
  const utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=hero-cta';
  const agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  const devinHref = type === 'schedule' ? 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + (prompt ? '&prompt=' + encodedPrompt : '') : type === 'review' ? 'https://app.devin.ai/review?' + utm : agent === 'ada' ? 'https://app.devin.ai/search?' + utm + '&noSubmit=true' + (prompt ? '&prompt=' + encodedPrompt : '') : devinUrl ? devinUrl.includes('?') ? devinUrl + '&' + utm + agentParams : devinUrl + '?' + utm + agentParams : prompt ? 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encodedPrompt : 'https://app.devin.ai/?' + utm + agentParams;
  const buttonLabel = type === 'schedule' ? 'Schedule in Devin ↗' : type === 'review' ? 'Set Up Devin Review ↗' : agent === 'advanced' ? 'Try in Devin ↗' : agent === 'dana' ? 'Try in Dana ↗' : agent === 'ada' ? 'Try in Ask Devin ↗' : 'Try in Devin ↗';
  const featureList = features ? features.split(',').map(f => f.trim()) : [];
  return <div className="uc-hero">
      <div className="uc-hero-inner">
        <div className="uc-hero-left">
          <h1 className="uc-hero-title">{title}</h1>
          <p className="uc-hero-desc">{description}</p>
          <div>
            <a href={devinHref} target="_blank" rel="noopener noreferrer" className="try-in-devin-btn">
              {buttonLabel}
            </a>
          </div>
        </div>
        <div className="uc-hero-meta">
          <div className="uc-meta-item">
            <span className="uc-meta-label">Author</span>
            <span className="uc-meta-value">Cognition</span>
          </div>
          <div className="uc-meta-item">
            <span className="uc-meta-label">Category</span>
            <span className="uc-meta-value">{category}</span>
          </div>
          {featureList.length > 0 && <div className="uc-meta-item">
              <span className="uc-meta-label">Features</span>
              <span className="uc-meta-value">{featureList.join(', ')}</span>
            </div>}
        </div>
      </div>
    </div>;
};

export const PromptBlock = ({children, type, agent, intent, playbookId}) => {
  var utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=prompt-block';
  var tag = 'docs-use-case-gallery';
  var agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  var label = type === 'schedule' ? 'Schedule in Devin' : type === 'playbook' ? 'Create Playbook' : type === 'knowledge' ? 'Add to Knowledge' : agent === 'advanced' ? 'Try in Devin' : agent === 'dana' ? 'Try in Dana' : agent === 'ada' ? 'Try in Ask Devin' : 'Try in Devin';
  var buildUrl = function (text) {
    var encoded = encodeURIComponent(text);
    if (type === 'schedule') return 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + '&prompt=' + encoded;
    if (type === 'playbook') return 'https://app.devin.ai/settings/playbooks/create?' + utm + '&body=' + encoded;
    if (type === 'knowledge') return 'https://app.devin.ai/knowledge?' + utm + '&body=' + encoded;
    if (agent === 'ada') return 'https://app.devin.ai/search?' + utm + '&noSubmit=true&prompt=' + encoded;
    return 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encoded;
  };
  const ref = React.useRef(null);
  const [href, setHref] = React.useState('#');
  React.useEffect(() => {
    if (!ref.current) return;
    var codeEl = ref.current.querySelector('pre code');
    if (codeEl) {
      var text = codeEl.textContent.trim();
      if (text) setHref(buildUrl(text));
    }
    var header = ref.current.querySelector('[data-component-part="code-block-header"]');
    if (header && !header.querySelector('.prompt-block-devin-link')) {
      var link = document.createElement('a');
      link.href = href;
      link.target = '_blank';
      link.rel = 'noopener noreferrer';
      link.className = 'prompt-block-devin-link';
      link.style.cssText = 'display:inline-flex;align-items:center;gap:6px;text-decoration:none;color:#fff;font-size:11px;font-weight:500;padding:4px 10px;border-radius:6px;white-space:nowrap;background:#317CFF;transition:background 0.2s;margin-left:8px;';
      link.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg> ' + label;
      link.onmouseenter = function () {
        link.style.background = '#2968D9';
      };
      link.onmouseleave = function () {
        link.style.background = '#317CFF';
      };
      header.appendChild(link);
    }
    var existingLink = ref.current.querySelector('.prompt-block-devin-link');
    if (existingLink && href !== '#') existingLink.href = href;
  });
  return <div className="prompt-block" ref={ref}>{children}</div>;
};

<UseCaseHero title="在 staging 环境每晚运行端到端 E2E 测试" description="安排 Devin 每晚在 staging 环境运行你的端到端测试套件，并为失败用例创建工单。" prompt="帮我设置一个每晚运行的 Devin 计划任务，在 staging 环境执行我们的 E2E 测试套件。请按照 https://docs.devin.ai/use-cases/gallery/api-scheduled-sessions 上的指南，带我完成每一步：创建服务用户、编写测试 Playbook，并通过 API 创建该计划。" category="自动化" features="API, 调度, Playbook" />

<div className="uc-detail-wrapper">
  <Tip>不想手动设置？将此页面的链接粘贴到 Devin 会话中，并让它为你完成所有设置。</Tip>

  本指南介绍如何通过 [v3 API](/zh/api-reference/v3/overview) 管理计划任务，适用于自动化和基础设施即代码工作流。您也可以直接在 [Devin UI](https://app.devin.ai/settings/schedules?utm_source=docs\&utm_medium=use-case-gallery) 中创建和管理计划任务，无需任何 API 配置。

  <Steps>
    <Step title="为 API 访问设置服务账号">
      通过 API 创建的预定会话需要具备相应权限的服务用户。你只需一次性完成设置，然后在下面的所有调用中使用该用户的 API key。

      1. 访问 [app.devin.ai](https://app.devin.ai/?utm_source=docs\&utm_medium=use-case-gallery) > **Settings** > **Service Users**，然后点击 **Create Service User**
      2. 分配包含 `ManageOrgSchedules` 权限的角色
      3. 保存创建后显示的 API key——它只会显示一次，之后你将使用它作为你的 `Bearer` token

      要获取你的 **organization ID**，请使用服务用户的 token 调用 [List Organizations](/zh/api-reference/v3/organizations/enterprise-organizations) 端点：

      ```bash theme={null}
      curl "https://api.devin.ai/v3/enterprise/organizations" \
        -H "Authorization: Bearer $DEVIN_API_KEY"
      ```

      导出这两个值，以便本指南中的命令可以按原样运行：

      ```bash theme={null}
      export DEVIN_API_KEY="sk-your-service-user-key"
      export ORG_ID="your-org-id"
      ```

      有关服务用户和权限的更多信息，请参阅[API 身份验证文档](/zh/api-reference/authentication)。
    </Step>

    <Step title="为本次测试运行编写运行手册">
      在创建计划之前，先编写一个[playbook](/zh/product-guides/creating-playbooks)，明确告诉 Devin 如何运行你的 E2E 测试套件，以及如何处理结果。前往 [**Settings > Playbooks**](https://app.devin.ai/settings/playbooks?utm_source=docs\&utm_medium=use-case-gallery) 并创建一个新的 playbook——或者让 Devin 根据你的测试工作流程描述为你生成一个。下面是一个适用于 Playwright 套件的示例：

      <PromptBlock type="playbook">
        ```txt Nightly E2E test playbook theme={null}
        You are running the nightly E2E test suite for acme/web-app against staging.

        Steps:
        1. Run the Playwright E2E suite targeting staging:
           `PLAYWRIGHT_BASE_URL=https://staging.acme.dev npx playwright test --reporter=json`
        2. Parse the JSON results. For each failing test:
           - Create a Linear ticket in the "QA" team with:
             - Title: "[Nightly E2E] <test name>"
             - Description: the full error message, stack trace, and the test file path
             - Label: "nightly-e2e-failure"
             - Priority: High if the test is in tests/critical/*, otherwise Medium
           - Attach any failure screenshots from the Playwright report
        3. If all tests pass, post a summary to the #qa-results Slack channel:
           "Nightly E2E: all <N> tests passed against staging (<date>)"
        4. If any tests fail, post to #qa-results:
           "Nightly E2E: <N> of <total> tests failed — <count> Linear tickets created"
           Include a list of the failing test names.
        ```
      </PromptBlock>

      保存后记下该 playbook 的 ID——在进行 API 调用时会用到它。在查看该 playbook 时，你可以在 URL 中找到它 (`app.devin.ai/.../playbooks/{playbook_id}`) 。

      <Tip>安装 [Linear integration](/zh/integrations/linear)，这样 Devin 就可以在 playbook 中创建工单。在[创建计划](https://app.devin.ai/settings/schedules/create?utm_source=docs\&utm_medium=use-case-gallery)时，你还可以设置一个 **Slack channel** (例如 `#qa-results`) ，让你的团队自动收到通知。如果测试需要，可通过 [organization secrets](/zh/product-guides/secrets) 为 Devin 提供对预发布环境机密 (数据库 URL、API key) 的只读访问权限。</Tip>
    </Step>

    <Step title="通过 API 创建夜间计划任务">
      现在使用 `POST /v3/organizations/{org_id}/schedules` 端点来注册该调度计划。此示例会在每天的 UTC 时间凌晨 2 点运行：

      ```bash theme={null}
      curl -X POST "https://api.devin.ai/v3/organizations/$ORG_ID/schedules" \
        -H "Authorization: Bearer $DEVIN_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "title": "Nightly E2E — staging",
          "prompt": "Run the nightly E2E test suite against staging. Follow the playbook exactly.",
          "schedule_type": "recurring",
          "frequency": "0 2 * * *",
          "playbook_id": "your-playbook-id"
        }'
      ```

      响应中包含一个用于后续管理该计划的 `schedule_id`。请先将其保存：

      ```bash theme={null}
      export SCHEDULE_ID="a1b2c3d4-e5f6-7890-abcd-ef1234567890"  # 来自响应结果
      ```

      `frequency` 字段使用标准 cron 语法。以下是一些常用示例：

      | Pattern       | When it runs        |
      | ------------- | ------------------- |
      | `0 2 * * *`   | 每天 UTC 时间凌晨 2 点     |
      | `0 2 * * 1-5` | 仅工作日 (周一至周五) 凌晨 2 点 |
      | `0 6 * * 1`   | 每周一 UTC 时间早上 6 点    |

      为什么是凌晨 2 点？你希望测试在当天最后一次部署在预发环境稳定之后再运行，但又要足够早，这样工程师开始工作时就能看到失败结果。请根据你团队的时区和部署节奏进行调整。

      完整字段列表请参见 [Create schedule 端点文档](/zh/api-reference/v3/schedules/post-organizations-schedules)。
    </Step>

    <Step title="验证首次运行并优化提示词">
      在计划首次执行后，检查该会话，确保 Devin 正确运行了测试，且输出结果符合你的预期。

      1. 打开 Devin 仪表盘，在 **Past Sessions** 下找到该会话——它会带有计划名称标签
      2. Playwright 测试套件是否已执行？是否只为真实失败 (而非不稳定测试) 创建了 Linear 工单？
      3. 在 `#qa-results` Slack 频道中查看汇总消息

      首次运行时的常见问题及其解决方法：

      * **Devin 无法访问预发布环境**：将你的预发布环境变量 (例如 `STAGING_API_KEY` 或 `DATABASE_URL`) 添加为[组织密钥](/zh/product-guides/secrets)，以便它们在每个计划会话中都可用
      * **由不稳定测试产生了过多工单**：在运行手册中添加重试逻辑："在创建工单前，先对任何失败的测试重新运行一次。只有在连续失败两次时才创建工单。"
      * **测试耗时过长**：收窄测试范围——例如："只运行 `tests/critical/` 和 `tests/smoke/` 中的测试"——或增加会话超时时间

      <PromptBlock agent="advanced">
        ```txt Prompt tweak: filter out flaky tests theme={null}
        更新夜间 E2E 运行手册：在为失败测试创建 Linear 工单之前，
        使用 `npx playwright test <test-file> --retries=1` 再运行一次该测试。
        仅当测试在重试时仍然失败时才创建工单。为在重试中通过的任何测试添加一个 "flaky" 标签，
        并在 Slack 汇总消息中提及这些测试。
        ```
      </PromptBlock>
    </Step>

    <Step title="以代码方式管理调度">
      一旦你的每晚任务运行稳定下来，你就会希望像管理其他计划任务一样管理它：在部署冻结期间将其暂停，在测试套件发生变化时更新提示词，或者为不同环境再创建一个计划任务。

      在部署冻结或维护窗口期间**暂停计划任务**：

      ```bash theme={null}
      curl -X PATCH "https://api.devin.ai/v3/organizations/$ORG_ID/schedules/$SCHEDULE_ID" \
        -H "Authorization: Bearer $DEVIN_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"enabled": false}'
      ```

      **在冻结结束后重新启用：**

      ```bash theme={null}
      curl -X PATCH "https://api.devin.ai/v3/organizations/$ORG_ID/schedules/$SCHEDULE_ID" \
        -H "Authorization: Bearer $DEVIN_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"enabled": true}'
      ```

      **列出所有计划任务**，以审查当前正在运行的任务：

      ```bash theme={null}
      curl "https://api.devin.ai/v3/organizations/$ORG_ID/schedules" \
        -H "Authorization: Bearer $DEVIN_API_KEY"
      ```

      对于需要管理多个计划任务的团队，可以让 Devin 构建一个 CLI，将计划任务定义从 YAML 配置文件中同步到系统中——这样你就可以像管理测试配置一样，对这些计划任务进行版本控制：

      <PromptBlock>
        ```txt Schedules-as-code CLI theme={null}
        Write a Python script that reads schedule definitions from a file called
        devin-schedules.yaml and syncs them to the Devin API.

        The YAML file should look like:
          schedules:
            - title: "Nightly E2E — staging"
              prompt: "Run the nightly E2E suite against staging."
              frequency: "0 2 * * *"
              playbook_id: "a1b2c3d4e5f6789012345678"
            - title: "Nightly E2E — preview"
              prompt: "Run the E2E suite against the preview environment."
              frequency: "0 3 * * 1-5"
              playbook_id: "a1b2c3d4e5f6789012345678"

        The script should:
        1. Compare the YAML definitions against existing schedules from the API
        2. Create new schedules, update changed ones, and disable schedules
           removed from the file
        3. Support a --dry-run flag that shows what would change
        4. Use the endpoints at /v3/organizations/{org_id}/schedules
        ```
      </PromptBlock>
    </Step>
  </Steps>
</div>
