Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ tests/mail/reports/
# Generated / test artifacts
internal/registry/meta_data.json
cmd/api/download.bin
app.log
94 changes: 92 additions & 2 deletions tests/cli_e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,96 @@ Put them under tests/cli_e2e/xxx.
## Run

```bash
make build
go test ./tests/cli_e2e/... -count=1
make e2e-test
```

JUnit report output:

```text
tests/cli_e2e/.artifacts/cli-e2e-report.xml
```

## Local User E2E Credentials

For `--as user` E2E runs, you can inject a portable credential file instead of
going through browser login.

Set `LARK_CLI_CREDENTIALS_FILE` to a JSON file like this:

```json
{
"appId": "cli_xxx",
"appSecret": "xxxx",
"brand": "lark",
"userOpenId": "ou_xxx",
"userName": "e2e user",
"accessToken": "",
"refreshToken": "u-xxxx",
"expiresAt": 0,
"refreshExpiresAt": 0,
"scope": "task:task:readonly",
"grantedAt": 0
}
```

When this env var is present, the CLI E2E harness will:

- create an isolated temporary HOME + `config.json`
- point child `lark-cli` processes at that temp config directory
- let the CLI read refresh/access token data from the same credentials file
- remove the temporary files after each command run

Example:

```bash
export LARK_CLI_CREDENTIALS_FILE=/tmp/lark-cli-user-creds.json
go test ./tests/cli_e2e/task -count=1
```

For GitHub Actions, store the same JSON content as a base64-encoded repository
secret such as `TEST_USER_CREDENTIALS_B64`, decode it into a temporary file at
runtime, export `LARK_CLI_CREDENTIALS_FILE`, and remove the file in an
`if: always()` cleanup step.

## Browser Auth E2E (Playwright)

`tests/cli_e2e/auth` contains config/auth entry-chain tests:

- `auth login --no-wait --json` -> browser authorization -> `auth login --device-code`
- `config init --new` -> parse verification URL from process output -> browser authorization -> `config show`

Playwright files live in `tests/cli_e2e/browser`.

Run locally:

```bash
cd tests/cli_e2e/browser
npm install

cd ../../..
export LARK_E2E_ENABLE_BROWSER_AUTH=1
go test ./tests/cli_e2e/auth -count=1 -v
```

If your OAuth page redirects to Feishu login (QR/password), provide an
authenticated Playwright storage state:

```bash
cd tests/cli_e2e/browser
npx playwright codegen https://open.feishu.cn --save-storage=.auth/state.json
```

Then run E2E with:

```bash
export PLAYWRIGHT_STORAGE_STATE=/Users/bytedance/cli/tests/cli_e2e/browser/.auth/state.json
export LARK_E2E_ENABLE_BROWSER_AUTH=1
go test ./tests/cli_e2e/auth -count=1 -v
```

When enabled, tests write artifacts to a temporary directory and print its path:

- `cli.stdout.log`
- `cli.stderr.log`
- `playwright.stdout.log`
- `playwright.stderr.log`
46 changes: 46 additions & 0 deletions tests/cli_e2e/base/base_advperm_workflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package base

import (
"context"
"testing"
"time"

clie2e "github.com/larksuite/cli/tests/cli_e2e"
"github.com/stretchr/testify/require"
)

func TestBase_AdvpermWorkflow(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
t.Cleanup(cancel)

baseToken := createBase(t, ctx, "lark-cli-e2e-base-advperm-"+testSuffix())

Comment on lines +15 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Isolate CLI config state for this E2E test.

Please set a per-test config directory at test start to avoid cross-test/config pollution.

 func TestBase_AdvpermWorkflow(t *testing.T) {
+	t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
 	ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
 	t.Cleanup(cancel)

As per coding guidelines **/*_test.go: "Isolate config state in Go tests by using t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestBase_AdvpermWorkflow(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
t.Cleanup(cancel)
baseToken := createBase(t, ctx, "lark-cli-e2e-base-advperm-"+testSuffix())
func TestBase_AdvpermWorkflow(t *testing.T) {
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
t.Cleanup(cancel)
baseToken := createBase(t, ctx, "lark-cli-e2e-base-advperm-"+testSuffix())
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_advperm_workflow_test.go` around lines 20 - 25, In
TestBase_AdvpermWorkflow, isolate CLI config state by calling
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) at the start of the test
(before createBase and other actions) so the test uses a unique per-test config
directory; add this single line near the top of the TestBase_AdvpermWorkflow
function (e.g., immediately after context setup) to prevent cross-test/config
pollution.

t.Run("enable", func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"base", "+advperm-enable", "--base-token", baseToken},
DefaultAs: "bot",
})
require.NoError(t, err)
if result.ExitCode != 0 {
skipIfBaseUnavailable(t, result, "requires bot advanced permission enable capability")
}
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)
})

t.Run("disable", func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"base", "+advperm-disable", "--base-token", baseToken, "--yes"},
DefaultAs: "bot",
})
require.NoError(t, err)
if result.ExitCode != 0 {
skipIfBaseUnavailable(t, result, "requires bot advanced permission disable capability")
}
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)
})
}
41 changes: 41 additions & 0 deletions tests/cli_e2e/base/base_core_workflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package base

import (
"context"
"testing"
"time"

clie2e "github.com/larksuite/cli/tests/cli_e2e"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
)

func TestBase_CoreWorkflow(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
t.Cleanup(cancel)

baseToken := createBase(t, ctx, "lark-cli-e2e-base-"+testSuffix())

t.Run("get base", func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"base", "+base-get", "--base-token", baseToken},
DefaultAs: "bot",
})
require.NoError(t, err)
if result.ExitCode != 0 {
skipIfBaseUnavailable(t, result, "requires bot base get capability")
}
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)
assert.NotEmpty(t, gjson.Get(result.Stdout, "data.base.name").String(), "stdout:\n%s", result.Stdout)
})

t.Run("copy base", func(t *testing.T) {
copiedToken := copyBase(t, ctx, baseToken, "lark-cli-e2e-base-copy-"+testSuffix())
assert.NotEqual(t, baseToken, copiedToken)
})
}
Loading
Loading