Skip to content

Add cli e2e tests coverage#400

Closed
yxzhaao wants to merge 18 commits intolarksuite:mainfrom
yxzhaao:feat/cli-e2e-tests-for-bot-subset
Closed

Add cli e2e tests coverage#400
yxzhaao wants to merge 18 commits intolarksuite:mainfrom
yxzhaao:feat/cli-e2e-tests-for-bot-subset

Conversation

@yxzhaao
Copy link
Copy Markdown
Contributor

@yxzhaao yxzhaao commented Apr 10, 2026

Summary

This PR adds a stable set of CLI E2E tests.

Covered commands:

  • base: +base-create, +base-get, +table-create, +table-get, +table-list, +advperm-enable, +role-list, +role-get, +role-update, +role-delete
  • calendar: +event-create, +calendar-create, +calendar-list, +calendar-get, +calendar-update, +agenda, +suggestion
  • contact: +get-user, plus raw api get /open-apis/contact/v3/users for bot-side user discovery
  • docs: +docs-create, +docs-get, +docs-update
  • drive: files create_folder, files move, permission.members auth, user subscription, user subscription_status, user remove_subscription
  • im: +chat-create, +chat-update, chats get, chats link, +messages-send, +messages-mget, +messages-reply
  • sheets: +sheet-create, +sheet-get, +sheet-update, +sheet-delete, spreadsheets get, +filter, +find
  • wiki: nodes create, nodes copy, nodes list, spaces get, spaces get_node, spaces list

Summary by CodeRabbit

  • Tests

    • Added extensive CLI end-to-end workflow tests and reusable test helpers across Base, Calendar, Contact, Docs, Drive, IM, Sheets, Task, and Wiki domains.
  • Documentation

    • Expanded E2E docs with domain-level coverage summaries, updated testcase guidance, browser-auth instructions, and notes on running/reporting e2e workflows.
  • Chores

    • Updated version control settings to ignore local app log files.

zhao.yuxuan and others added 17 commits April 7, 2026 20:21
Change-Id: I43922a6cce5a671e842e48e57e8fb77aae738647
Change-Id: Ic1f8f4fc4a28499c20154b3f7e3a4b5f4ddc46f2
Change-Id: I966c752cec04d52c6af76348b9227ec9e97f4ca4
Change-Id: Id5df27eca7c99c0d0caa46b29b64be30e6f7a29a
Change-Id: I49dc33a0f6639a7ca380373973f0cc2d874e34cd
…/cli into feat/cli-shortcut-e2e-tests

Change-Id: Id9b41f5297067ad7fbc00b4d668a7458d5a08058
Change-Id: I6ddaa82e399a8baba277232bdd71a33eea262958
Change-Id: Id66f292991898650a6fa83bd225e83f687e4ee22
…ummary

Change-Id: Ie04e887811f34a1f99dd9fb9d08b68db9b3f176a
Change-Id: Iae2774707ea6c7ba11717f0f711f72f8898e09ea
Change-Id: If8dc8da97977f1be869ebfe48cd69350d77694ed
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 10, 2026

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
3 out of 5 committers have signed the CLA.

✅ yaozhen00
✅ yxzhaao
✅ jinzemo
❌ zhao.yuxuan
❌ iFish007


zhao.yuxuan seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions github-actions Bot added the size/M Single-domain feat or fix with limited business impact label Apr 10, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3dd8523-2eca-432c-9d90-2ccc6fc87751

📥 Commits

Reviewing files that changed from the base of the PR and between 0a268fd and b97e307.

📒 Files selected for processing (7)
  • tests/cli_e2e/base/base_basic_workflow_test.go
  • tests/cli_e2e/calendar/calendar_find_meeting_time_test.go
  • tests/cli_e2e/contact/contact_shortcut_test.go
  • tests/cli_e2e/drive/drive_files_workflow_test.go
  • tests/cli_e2e/drive/drive_permission_user_workflow_test.go
  • tests/cli_e2e/im/chat_workflow_test.go
  • tests/cli_e2e/im/message_workflow_test.go

📝 Walkthrough

Walkthrough

Adds extensive CLI end-to-end tests, helpers, and coverage/docs across multiple domains (base, calendar, contact, docs, drive, im, sheets, task, wiki) and a small .gitignore update to ignore app.log.

Changes

Cohort / File(s) Summary
Configuration
\.gitignore
Added app.log to ignore list.
Test Harness & Docs
tests/cli_e2e/README.md, tests/cli_e2e/cli-e2e-testcase-writer/SKILL.md, tests/cli_e2e/coverage.md, tests/cli_e2e/demo/coverage.md
Reworked E2E run/docs: make e2e-test, credentials-file handling, browser-auth notes, and standardized per-domain coverage guidance and templates.
Base
tests/cli_e2e/base/... (base_basic_workflow_test.go, base_role_workflow_test.go, helpers_test.go)
Added base CRUD and role workflow tests and extensive helper utilities for resource creation, ID extraction, conditional skips, and cleanup.
Calendar
tests/cli_e2e/calendar/... (calendar_create_event_test.go, calendar_find_meeting_time_test.go, calendar_manage_calendar_test.go, calendar_view_agenda_test.go, helpers_test.go)
Added event create/delete, meeting suggestion, calendar CRUD, agenda views, and helpers for event creation and primary calendar lookup.
Contact
tests/cli_e2e/contact/contact_shortcut_test.go
Added bot-based user lookup E2E test using contact shortcut and API discovery step.
Docs
tests/cli_e2e/docs/... (docs_create_fetch_test.go, docs_update_test.go)
Added document create/fetch/update workflows with content/title verification and cleanup.
Drive
tests/cli_e2e/drive/... (drive_files_workflow_test.go, drive_move_workflow_test.go, drive_permission_user_workflow_test.go, helpers_test.go)
Added folder create, move (with task polling), permission auth, subscription tests, and helpers for temp files, upload, and import with cleanup.
IM
tests/cli_e2e/im/... (chat_workflow_test.go, message_workflow_test.go, helpers_test.go)
Added chat creation (options), send/reply/message retrieval/link workflows and helpers for chat/message operations.
Sheets
tests/cli_e2e/sheets/... (sheets_crud_workflow_test.go, sheets_filter_workflow_test.go)
Added spreadsheet CRUD (create/info/write/read/append/find/export), filter create/update/delete, and find workflows.
Task
tests/cli_e2e/task/... (coverage.md, multiple _test.go)
Added task coverage doc and user-scoped/get-my-tasks test plus workflow comment blocks for status/reminder/comment/tasklist tests.
Wiki
tests/cli_e2e/wiki/... (wiki_workflow_test.go, helpers_test.go)
Added wiki node workflows (create/get/list/copy/search) and helpers to parse JSON and skip when feature unavailable.

Sequence Diagram(s)

(Skipped — changes are test additions and helpers without a new cross-component runtime control flow requiring visualization.)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

domain/base, domain/im, domain/calendar, domain/task, domain/ccm

Suggested reviewers

  • tuxedomm
  • liangshuo-1

Poem

🐰
I hopped through tests both far and near,
Adding helpers, workflows, and cheer.
From Calendar to Drive, the suites now play,
Running clean at night and bright by day.
Hooray for coverage — carrot cake, hooray!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description provides a solid summary of covered commands across multiple domains and the objectives, but the provided template structure is largely incomplete with placeholder text ('Change 1', 'Change 2') and unchecked test plan items.
Title check ✅ Passed The title 'Add cli e2e tests coverage' directly and clearly describes the main change—adding end-to-end test coverage for the CLI. It is specific, concise, and accurately reflects the primary objective of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@yxzhaao yxzhaao changed the title Add Add CLI E2E Apr 10, 2026
@yxzhaao yxzhaao changed the title Add CLI E2E Add cli e2e tests coverage Apr 10, 2026
Change-Id: Id5a19567fe24124eb75a56f96b484297b2f99c13
@yxzhaao yxzhaao force-pushed the feat/cli-e2e-tests-for-bot-subset branch from 0a268fd to b97e307 Compare April 10, 2026 10:02
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 10, 2026

Greptile Summary

This PR adds comprehensive CLI E2E test coverage across Base, Calendar, Contact, Docs, Drive, IM, Sheets, Task, and Wiki domains, introducing ~30 new test files with well-structured helper utilities and graceful capability skipping. The overall implementation is solid — the base helpers in particular show a mature cleanup pattern — but there is one resource-leak defect in the drive helpers worth addressing before merge.

  • P1 – uploadTestFile cleanup silently fails: the drive files delete call omits "type": "file", while the companion importTestDoc and folder cleanup calls both pass their respective types explicitly; uploaded test files will accumulate with every run.
  • P2 – 10 new Go files missing trailing newlines: affects gofmt compliance checks in CI.
  • P2 – Three unused helper functions in im/helpers_test.go (sendImage, createChatWithBotManager, replyInThread).

Confidence Score: 4/5

Safe to merge after fixing the drive file-delete cleanup missing type parameter; all other findings are P2.

One P1 defect (uploadTestFile cleanup omits required type param, causing silent resource leak on every CI run) warrants a 4 rather than 5. All other findings are cosmetic/style P2.

tests/cli_e2e/drive/helpers_test.go (missing type param in cleanup), tests/cli_e2e/im/helpers_test.go (unused helpers + missing EOF newline)

Important Files Changed

Filename Overview
tests/cli_e2e/drive/helpers_test.go New drive test helpers; uploadTestFile cleanup omits "type": "file" param, causing silent cleanup failures unlike the folder/doc helpers which correctly pass type.
tests/cli_e2e/im/helpers_test.go New IM test helpers; contains three unused functions (sendImage, createChatWithBotManager, replyInThread) and is missing a trailing newline.
tests/cli_e2e/im/chat_workflow_test.go New IM chat workflow tests; TestIM_ChatCreateWithOptionsWorkflow creates two chats without any cleanup registration.
tests/cli_e2e/base/helpers_test.go Comprehensive base test helpers with well-structured cleanup patterns using cleanupContext; second-granularity testSuffix is a minor collision risk.
tests/cli_e2e/calendar/calendar_create_event_test.go New calendar event creation test; missing trailing newline; the verify delete acknowledged step uses _ = result which provides no meaningful assertion.
tests/cli_e2e/wiki/wiki_workflow_test.go New wiki workflow tests covering node create, copy, list, and space operations; clean implementation with proper skip logic for unavailable environments.
tests/cli_e2e/base/base_basic_workflow_test.go New base CRUD workflow test covering base-create, base-get, table-create, table-get, table-list; uses skipIfBaseUnavailable for graceful capability skipping.
tests/cli_e2e/sheets/sheets_crud_workflow_test.go New sheets CRUD workflow covering create, read, write, append, find, export; missing trailing newline.
tests/cli_e2e/task/task_get_my_tasks_user_test.go New task test requiring user identity; correctly skips when no logged-in user is available by pre-checking auth status.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph setup["Test Setup (helpers_test.go)"]
        H1[createBase / createTable / createRole]
        H2[uploadTestFile / importTestDoc]
        H3[createChat / sendMessage]
        H4[getPrimaryCalendarID / createEvent]
    end

    subgraph domains["E2E Test Domains"]
        B[Base: basic + role workflows]
        CAL[Calendar: event create, manage, agenda, suggestion]
        CON[Contact: get-user bot workflow]
        DOC[Docs: create+fetch, update]
        DRV[Drive: files, move, permission, subscription]
        IM[IM: chat, messages]
        SH[Sheets: CRUD, filter, find]
        TSK[Task: status, reminder, comment, tasklist, my-tasks]
        WK[Wiki: node + space workflow]
    end

    subgraph cleanup["Cleanup (parentT.Cleanup)"]
        C1[delete table / field / record / view]
        C2[delete drive files - P1: missing type param for regular files]
        C3[delete calendar events]
        C4[chat: no-op - deletion unavailable]
    end

    setup --> domains
    domains --> cleanup

    style C2 fill:#ffcccc,stroke:#cc0000
Loading

Reviews (1): Last reviewed commit: "feat: add stable bot-only cli e2e subset" | Re-trigger Greptile

Comment on lines +71 to +75
// The imported document is registered for cleanup via parentT.Cleanup.
func importTestDoc(t *testing.T, parentT *testing.T, ctx context.Context, suffix, content string) string {
t.Helper()

testDir := filepath.Join("tests", "cli_e2e", "drive", "testfiles")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Missing "type" parameter in file cleanup

uploadTestFile omits the "type" param from its cleanup call to drive files delete, while both the folder and doc cleanup helpers in the same file explicitly pass it ("type": "folder" and "type": "docx"). If the Lark API requires this parameter for file deletion (it does for folders and docs), the cleanup call will fail silently every time, leaving uploaded test files behind.

Suggested change
// The imported document is registered for cleanup via parentT.Cleanup.
func importTestDoc(t *testing.T, parentT *testing.T, ctx context.Context, suffix, content string) string {
t.Helper()
testDir := filepath.Join("tests", "cli_e2e", "drive", "testfiles")
parentT.Cleanup(func() {
clie2e.RunCmd(context.Background(), clie2e.Request{
Args: []string{"drive", "files", "delete"},
Params: map[string]any{"file_token": fileToken, "type": "file"},
})
})

Comment on lines +97 to +172
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

messageID := gjson.Get(result.Stdout, "data.message_id").String()
require.NotEmpty(t, messageID, "message_id should not be empty")

return messageID
}

// sendImage sends an image message to the specified chat and returns the messageID.
func sendImage(t *testing.T, parentT *testing.T, ctx context.Context, chatID string, imagePath string) string {
t.Helper()

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"im", "+messages-send",
"--chat-id", chatID,
"--image", imagePath,
},
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

messageID := gjson.Get(result.Stdout, "data.message_id").String()
require.NotEmpty(t, messageID, "message_id should not be empty")

return messageID
}

// replyMessage sends a reply to a message and returns the reply messageID.
func replyMessage(t *testing.T, parentT *testing.T, ctx context.Context, messageID string, text string) string {
t.Helper()

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"im", "+messages-reply",
"--message-id", messageID,
"--text", text,
},
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

replyMessageID := gjson.Get(result.Stdout, "data.message_id").String()
require.NotEmpty(t, replyMessageID, "reply message_id should not be empty")

return replyMessageID
}

// replyInThread sends a reply in thread to a message and returns the reply messageID.
func replyInThread(t *testing.T, parentT *testing.T, ctx context.Context, messageID string, text string) string {
t.Helper()

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"im", "+messages-reply",
"--message-id", messageID,
"--text", text,
"--reply-in-thread",
},
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

replyMessageID := gjson.Get(result.Stdout, "data.message_id").String()
require.NotEmpty(t, replyMessageID, "reply message_id should not be empty")

return replyMessageID
}

// generateSuffix generates a unique suffix based on current timestamp.
func generateSuffix() string {
return time.Now().UTC().Format("20060102-150405")
} No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Unused helper functions

sendImage, createChatWithBotManager, and replyInThread are defined in this file but are not called anywhere in the PR. Go does not error on unused functions in test files, but they increase maintenance surface and are a dead-code smell. Either use them in tests or remove them.

Comment on lines +67 to +104

t.Run("create chat with set-bot-manager", func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"im", "+chat-create",
"--name", chatName,
"--type", "private",
"--set-bot-manager",
},
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

chatID := gjson.Get(result.Stdout, "data.chat_id").String()
require.NotEmpty(t, chatID, "chat_id should not be empty")
})

t.Run("create public chat with description", func(t *testing.T) {
publicChatName := chatName + "-public"
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{"im", "+chat-create",
"--name", publicChatName,
"--type", "public",
"--description", "Test public chat for e2e",
},
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

publicChatID := gjson.Get(result.Stdout, "data.chat_id").String()
require.NotEmpty(t, publicChatID)
})
}

// TestIM_ChatUpdateWorkflow tests the +chat-update shortcut.
func TestIM_ChatUpdateWorkflow(t *testing.T) {
parentT := t
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Orphaned chats from TestIM_ChatCreateWithOptionsWorkflow

This test creates two chats (one with --set-bot-manager, one public) but neither is cleaned up — the parentT.Cleanup in createChat is a no-op comment noting that deletion is unavailable. However, TestIM_ChatCreateWithOptionsWorkflow doesn't even call createChat; it calls RunCmd directly with no cleanup at all, so these chats will accumulate in the test tenant with every run. Consider registering a best-effort delete if the API exposes it, or at minimum using the shared createChat helper so the no-op cleanup comment is at least consistent.

// Note: API may have eventual consistency - delete acknowledged but get may still succeed briefly
_ = result
})
} No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Missing trailing newline (multiple files)

calendar_create_event_test.go, calendar/helpers_test.go, calendar_manage_calendar_test.go, drive/drive_move_workflow_test.go, drive/helpers_test.go, docs/docs_create_fetch_test.go, docs/docs_update_test.go, im/helpers_test.go, sheets/sheets_crud_workflow_test.go, and sheets/sheets_filter_workflow_test.go all end without a trailing newline. gofmt (and most lint checks in CI) expect a final newline in every Go source file. Run gofmt -w on these files.

Comment on lines +165 to +175
}

func createTable(t *testing.T, parentT *testing.T, ctx context.Context, baseToken string, name string, fieldsJSON string, viewJSON string) (tableID string, primaryFieldID string, primaryViewID string) {
t.Helper()

args := []string{"base", "+table-create", "--base-token", baseToken, "--name", name}
if fieldsJSON != "" {
args = append(args, "--fields", fieldsJSON)
}
if viewJSON != "" {
args = append(args, "--view", viewJSON)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Second-granularity suffix can collide during concurrent package runs

testSuffix() (and the equivalent generateSuffix() in the im package) uses time.Now().UTC().Format("20060102-150405"), which has one-second resolution. If two test packages start within the same second, their resource names can collide (e.g., two lark-cli-e2e-base-role-20260410-123456 bases). Adding nanoseconds or a small random component would make suffixes collision-safe:

func testSuffix() string {
    return fmt.Sprintf("%s-%04d", time.Now().UTC().Format("20060102-150405"), time.Now().Nanosecond()/100000)
}

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (12)
.gitignore (1)

37-37: LGTM!

Adding app.log to the ignore list is appropriate for preventing application logs from being committed. The placement under the "# Generated / test artifacts" section is logical.

Note: The current pattern app.log only ignores this file at the repository root. If app.log files might be generated in subdirectories during E2E tests or other operations, consider using a recursive pattern instead.

📝 Optional: Make the pattern recursive

If app.log files could appear in subdirectories:

-app.log
+**/app.log

or simply:

-app.log
+app.log
+**/*/app.log

However, if the log file is only ever created at the root level, the current pattern is perfectly fine.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 37, The .gitignore currently has a root-only entry
"app.log"; to also ignore app.log files generated in subdirectories (e.g.,
during E2E tests) add a recursive pattern such as "**/app.log" (or replace
"app.log" with "**/app.log") so that the ignore applies to all directories;
update the .gitignore entry referencing the existing "app.log" line accordingly.
tests/cli_e2e/drive/helpers_test.go (2)

18-19: Unused constant testFileDir.

This constant is defined but never used—the path is duplicated inline at lines 27 and 75. Either remove the constant or use it consistently.

♻️ Suggested fix: use the constant
-	testDir := filepath.Join("tests", "cli_e2e", "drive", "testfiles")
+	testDir := testFileDir

Apply the same change in importTestDoc at line 75.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/drive/helpers_test.go` around lines 18 - 19, The constant
testFileDir is declared but not used; replace the duplicated inline string
literals with this constant: update any occurrences of
"tests/cli_e2e/drive/testfiles" (notably inside the function importTestDoc and
the other test helper) to use testFileDir instead so the path is centralized;
keep the const declaration as-is and ensure importTestDoc and any other helpers
reference testFileDir.

27-28: Consider checking os.MkdirAll error.

Ignoring the error from os.MkdirAll could mask CI permission issues or filesystem problems. The same pattern appears at line 76.

🛡️ Proposed fix
 	testDir := filepath.Join("tests", "cli_e2e", "drive", "testfiles")
-	_ = os.MkdirAll(testDir, 0755)
+	require.NoError(t, os.MkdirAll(testDir, 0755))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/drive/helpers_test.go` around lines 27 - 28, The calls to
os.MkdirAll (creating testDir) ignore errors and can hide CI/filesystem
failures; update both occurrences to check the returned error and fail the test
on error (use t.Fatalf or t.Fatal with a message including the error) so the
test explicitly handles and reports permission or filesystem issues when
creating the test directory referenced by testDir and the similar call later in
the file.
tests/cli_e2e/base/helpers_test.go (2)

510-522: Consider using t.TempDir() instead of current working directory.

Writing temporary files to the current working directory (os.Getwd()) can cause issues with parallel test execution and leaves files in potentially shared locations. Using t.TempDir() provides automatic cleanup and test isolation.

♻️ Proposed fix
 func writeTempAttachment(t *testing.T, content string) string {
 	t.Helper()
 
-	wd, err := os.Getwd()
-	require.NoError(t, err)
-
-	path := filepath.Join(wd, "attachment-"+testSuffix()+".txt")
+	path := filepath.Join(t.TempDir(), "attachment-"+testSuffix()+".txt")
 	err = os.WriteFile(path, []byte(content), 0o644)
 	require.NoError(t, err)
-	t.Cleanup(func() {
-		_ = os.Remove(path)
-	})
-	return "./" + filepath.Base(path)
+	return path
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/helpers_test.go` around lines 510 - 522,
writeTempAttachment currently creates the temp file in the current working
directory which can conflict across tests; change it to use t.TempDir() to
create an isolated directory, build the file path with filepath.Join(tempDir,
"attachment-"+testSuffix()+".txt"), write the file via os.WriteFile, and remove
the manual t.Cleanup removal (t.TempDir handles cleanup) so the function returns
"./"+filepath.Base(path) or simply the path as needed by callers; update
references to writeTempAttachment if they rely on cwd-relative paths.

491-508: Missing cleanup registration for created workflow.

Unlike other create* helpers in this file, createWorkflow doesn't take a parentT *testing.T parameter and doesn't register cleanup. This will leave test workflows behind after test execution.

If workflow deletion is supported, consider adding cleanup:

♻️ Proposed fix
-func createWorkflow(t *testing.T, ctx context.Context, baseToken string, body string) string {
+func createWorkflow(t *testing.T, parentT *testing.T, ctx context.Context, baseToken string, body string) string {
 	t.Helper()
 
 	result, err := clie2e.RunCmd(ctx, clie2e.Request{
 		Args:      []string{"base", "+workflow-create", "--base-token", baseToken, "--json", body},
 		DefaultAs: "bot",
 	})
 	require.NoError(t, err)
 	if result.ExitCode != 0 {
 		skipIfBaseUnavailable(t, result, "requires bot workflow create capability")
 	}
 	result.AssertExitCode(t, 0)
 	result.AssertStdoutStatus(t, true)
 
 	workflowID := gjson.Get(result.Stdout, "data.workflow_id").String()
 	require.NotEmpty(t, workflowID, "stdout:\n%s", result.Stdout)
+
+	parentT.Cleanup(func() {
+		cleanupCtx, cancel := cleanupContext()
+		defer cancel()
+
+		deleteResult, deleteErr := clie2e.RunCmd(cleanupCtx, clie2e.Request{
+			Args:      []string{"base", "+workflow-delete", "--base-token", baseToken, "--workflow-id", workflowID, "--yes"},
+			DefaultAs: "bot",
+		})
+		if deleteErr != nil || deleteResult.ExitCode != 0 {
+			reportCleanupFailure(parentT, "delete workflow "+workflowID, deleteResult, deleteErr)
+		}
+	})
+
 	return workflowID
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/helpers_test.go` around lines 491 - 508, createWorkflow
currently doesn't register cleanup so created workflows linger; change the
helper to accept a parentT *testing.T (or another *testing.T parameter) and
register parentT.Cleanup to delete the workflow after the test completes. After
obtaining workflowID in createWorkflow, invoke parentT.Cleanup with a function
that calls clie2e.RunCmd (similar to the create call) to run "base
+workflow-delete --base-token <baseToken> --workflow-id <workflowID>" (and
handle non-zero exit like skipIfBaseUnavailable if necessary), ensuring deletion
is attempted but does not fail the test if the base is unavailable.
tests/cli_e2e/sheets/sheets_filter_workflow_test.go (1)

175-268: Minor: parentT variable is unused.

The parentT variable on line 177 is assigned but never used since the cleanup callback is empty. Consider removing it for cleaner code.

♻️ Proposed fix
 func TestSheets_FindWorkflow(t *testing.T) {
-	parentT := t
 	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
 	t.Cleanup(cancel)
 ...
 	t.Run("create spreadsheet", func(t *testing.T) {
 ...
-		parentT.Cleanup(func() {
+		t.Cleanup(func() {
 			// Best-effort cleanup
 		})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/sheets/sheets_filter_workflow_test.go` around lines 175 - 268,
The local variable parentT in TestSheets_FindWorkflow is unused; remove the
parentT declaration and the empty parentT.Cleanup callback inside the "create
spreadsheet" t.Run block. Edit the TestSheets_FindWorkflow function to delete
the line creating parentT and the parentT.Cleanup(...) block so only the
existing t and its real cleanup (ctx cancel) remain; no other logic changes to
the "create spreadsheet" subtest are required.
tests/cli_e2e/wiki/helpers_test.go (1)

57-73: Missing cleanup for created wiki node.

The createWikiNode helper creates a wiki node but doesn't register a cleanup callback to delete it. Other helper functions in this PR (e.g., createTable, createField, createRecord in base/helpers_test.go) consistently register parentT.Cleanup to delete created resources.

Consider adding a parentT *testing.T parameter and registering cleanup to delete the wiki node, or document why cleanup is intentionally omitted (e.g., if wiki nodes cannot be deleted via CLI).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/wiki/helpers_test.go` around lines 57 - 73, The helper
createWikiNode currently returns a created node but never cleans it up; change
its signature to accept parentT *testing.T (e.g., createWikiNode(parentT
*testing.T, t *testing.T, ctx context.Context, req clie2e.Request) or swap
params to match callers), and after obtaining node (from wikiJSONPayload and
gjson.Get "data.node") register parentT.Cleanup to run a clie2e.RunCmd delete
request that deletes the node (use the node ID from the node result); keep
existing skipIfWikiUnavailable and result assertions unchanged so deletion only
registers when creation succeeded. Ensure callers are updated to pass parentT
and that the cleanup invokes the same CLI path used elsewhere for removing wiki
nodes.
tests/cli_e2e/calendar/helpers_test.go (1)

39-54: Cleanup callback lacks timeout context.

The cleanup callback uses context.Background() without a timeout, which could cause the test to hang if the delete API is unresponsive. Other helper files (e.g., base/helpers_test.go) use a dedicated cleanupContext() with a 30-second timeout.

♻️ Proposed fix
+const cleanupTimeout = 30 * time.Second
+
+func cleanupContext() (context.Context, context.CancelFunc) {
+	return context.WithTimeout(context.Background(), cleanupTimeout)
+}
+
 	parentT.Cleanup(func() {
-		deleteResult, deleteErr := clie2e.RunCmd(context.Background(), clie2e.Request{
+		cleanupCtx, cancel := cleanupContext()
+		defer cancel()
+
+		deleteResult, deleteErr := clie2e.RunCmd(cleanupCtx, clie2e.Request{
 			Args: []string{"calendar", "events", "delete"},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/calendar/helpers_test.go` around lines 39 - 54, The cleanup
callback uses context.Background() which can hang; change the RunCmd call in the
parentT.Cleanup closure to use the timeout context helper (cleanupContext())
instead of context.Background() so the delete request uses the 30s
timeout—update the clie2e.RunCmd invocation inside the parentT.Cleanup block
(the calendar events delete call that passes calendar_id/event_id) to call
cleanupContext() like other helpers (see base/helpers_test.go) so the cleanup
will time out reliably.
tests/cli_e2e/base/base_basic_workflow_test.go (1)

66-67: Consider adding fallback for table_id field.

The helper function createTable in helpers_test.go (lines 189-192) uses a fallback pattern checking both data.table.id and data.table.table_id. This assertion only checks data.table.id, which may fail if the API returns table_id instead.

♻️ Suggested fix to add fallback
-		assert.Equal(t, tableID, gjson.Get(result.Stdout, "data.table.id").String())
+		returnedTableID := gjson.Get(result.Stdout, "data.table.id").String()
+		if returnedTableID == "" {
+			returnedTableID = gjson.Get(result.Stdout, "data.table.table_id").String()
+		}
+		assert.Equal(t, tableID, returnedTableID)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_basic_workflow_test.go` around lines 66 - 67, The
assertion only checks gjson.Get(result.Stdout, "data.table.id") and should also
accept the alternative "data.table.table_id" like the createTable helper does;
update the check that compares to tableID to read either
gjson.Get(result.Stdout, "data.table.id").String() or gjson.Get(result.Stdout,
"data.table.table_id").String() (fall back to the latter when the former is
empty) while keeping the tableName assertion unchanged so tests pass regardless
of which field the API returns.
tests/cli_e2e/im/helpers_test.go (1)

69-86: Unused parentT parameter.

The parentT parameter is declared but never used in sendMessage. The same applies to sendMarkdown, sendImage, replyMessage, and replyInThread. If these are kept for API consistency or future cleanup, consider adding a brief comment. Otherwise, remove them.

♻️ Option 1: Remove unused parameter
-func sendMessage(t *testing.T, parentT *testing.T, ctx context.Context, chatID string, text string) string {
+func sendMessage(t *testing.T, ctx context.Context, chatID string, text string) string {

Apply similarly to sendMarkdown, sendImage, replyMessage, and replyInThread.

♻️ Option 2: Add comment explaining the signature
 // sendMessage sends a text message to the specified chat and returns the messageID.
+// parentT is accepted for API consistency with other helpers but is unused since message cleanup is not implemented.
 func sendMessage(t *testing.T, parentT *testing.T, ctx context.Context, chatID string, text string) string {
+	_ = parentT // unused: message cleanup not available via CLI
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/im/helpers_test.go` around lines 69 - 86, The helper functions
declare an unused parentT parameter—remove the unused parentT from the
signatures of sendMessage, sendMarkdown, sendImage, replyMessage, and
replyInThread and update all call sites to drop that argument; ensure you only
change the function signatures and their invocations (e.g., calls to
sendMessage(ctx, chatID, text) instead of sendMessage(parentT, ctx, chatID,
text)) so tests compile and no unused parameter warnings remain.
tests/cli_e2e/calendar/calendar_create_event_test.go (1)

94-107: The "verify delete acknowledged" step doesn't verify anything.

The test runs the get command but discards the result with _ = result. If the intent is to verify eventual consistency, consider either:

  1. Removing this step if it adds no value
  2. Adding a brief comment explaining why the result is intentionally ignored
  3. Actually checking for an expected error status indicating the event is deleted
♻️ Option: Remove or clarify the step
-	// Step 5: Verify delete was acknowledged (event may have eventual consistency)
-	t.Run("verify delete acknowledged", func(t *testing.T) {
-		require.NotEmpty(t, eventID)
-		result, err := clie2e.RunCmd(ctx, clie2e.Request{
-			Args: []string{"calendar", "events", "get"},
-			Params: map[string]any{
-				"calendar_id": calendarID,
-				"event_id":    eventID,
-			},
-		})
-		require.NoError(t, err)
-		// Note: API may have eventual consistency - delete acknowledged but get may still succeed briefly
-		_ = result
-	})

Or if keeping it, make the intent clearer:

 	t.Run("verify delete acknowledged", func(t *testing.T) {
 		require.NotEmpty(t, eventID)
-		result, err := clie2e.RunCmd(ctx, clie2e.Request{
+		// Best-effort check: command execution should succeed even if event is already deleted
+		// We don't assert on the response due to eventual consistency
+		_, err := clie2e.RunCmd(ctx, clie2e.Request{
 			Args: []string{"calendar", "events", "get"},
 			Params: map[string]any{
 				"calendar_id": calendarID,
 				"event_id":    eventID,
 			},
 		})
 		require.NoError(t, err)
-		// Note: API may have eventual consistency - delete acknowledged but get may still succeed briefly
-		_ = result
 	})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/calendar/calendar_create_event_test.go` around lines 94 - 107,
The "verify delete acknowledged" subtest currently calls clie2e.RunCmd in
tests/cli_e2e/calendar/calendar_create_event_test.go and then discards the
response (result) which means it verifies nothing; update the subtest (named
"verify delete acknowledged") to either remove the test entirely or assert a
meaningful outcome: for example, call clie2e.RunCmd with Args
{"calendar","events","get"} and Params {"calendar_id": calendarID, "event_id":
eventID} and then assert the expected deletion behavior (e.g., require.Error(t,
err) and check the error indicates NotFound or require.Contains(t,
result.StdErr, "not found") if the CLI returns non-zero output), or add a short
comment explaining that the response is intentionally ignored for eventual
consistency — reference the variables eventID, calendarID and the clie2e.RunCmd
invocation when making the change.
tests/cli_e2e/im/chat_workflow_test.go (1)

115-137: Verify persisted state after chat updates, not only success envelopes.

Both update subtests assert only exit/status. Add a follow-up im chats get assertion for name/description to actually prove mutation persisted.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/im/chat_workflow_test.go` around lines 115 - 137, The tests
"update chat name" and "update chat description" only assert success envelopes;
after calling clie2e.RunCmd with Args ["im", "+chat-update", "--chat-id",
chatID, "--name", updatedName] and similarly for description, add a follow-up
clie2e.RunCmd invocation that runs ["im", "chats", "get", "--chat-id", chatID]
and assert the returned stdout contains the updated value (updatedName or
updatedDescription) and still exits 0; update the subtests to call RunCmd for
"im chats get", parse/inspect stdout, and use require/assert helpers to validate
the persisted field so the mutation is actually verified.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/cli_e2e/base/base_role_workflow_test.go`:
- Line 35: The test creates a role via createRole(...) which already registers a
parentT.Cleanup delete (see parentT.Cleanup in helpers_test.go), so avoid
double-deleting by either removing the explicit "+role-delete" subtest or
changing the create call to a non-cleanup variant; locate the role creation line
where roleID := createRole(t, parentT, ctx, baseToken,
`{"role_name":"`+roleName+`","role_type":"custom_role"}`) and either (A) delete
the subtest that calls the workflow "+role-delete" later in this test, or (B)
replace createRole with a helper that does not register parentT.Cleanup so the
explicit delete remains — apply the same change for the other occurrence around
lines 118-129.
- Around line 17-20: The test TestBase_RoleWorkflow currently doesn't isolate
CLI config and can leak state; update the test setup to set a unique config
directory by calling t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) early in
TestBase_RoleWorkflow (before any CLI operations or goroutines) so each run uses
an isolated config dir; ensure this is added alongside the existing
context/cancel setup using the test's t variable.

In `@tests/cli_e2e/calendar/calendar_find_meeting_time_test.go`:
- Around line 16-19: In TestCalendar_FindMeetingTime, the CLI config state isn't
isolated and can leak between tests; set a test-local config dir by calling
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) near the start of the test
(after ctx/cancel creation) so all CLI invocations in
TestCalendar_FindMeetingTime use an isolated config directory.

In `@tests/cli_e2e/calendar/calendar_view_agenda_test.go`:
- Around line 16-18: In TestCalendar_ViewAgenda, isolate per-test config state
by setting the LARKSUITE_CLI_CONFIG_DIR to a temp directory before any CLI
invocation: call t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) at the start
of the TestCalendar_ViewAgenda function (after ctx creation is fine) so the test
reads/writes only test-local config and avoids shared state in parallel/CI runs.

In `@tests/cli_e2e/cli-e2e-testcase-writer/SKILL.md`:
- Line 107: Update the phrasing to use hyphenation for readability: replace the
phrase "go test -run friendly form" with "`go test -run`-friendly form" in the
SKILL.md entry (the line that currently reads "Write testcase entries in `go
test -run` friendly form.").

In `@tests/cli_e2e/docs/docs_create_fetch_test.go`:
- Around line 43-45: The empty parentT.Cleanup is leaving test-created documents
persisted; track created document IDs in the test (e.g., a slice named
createdDocIDs populated when calling the create routine) and implement a
best-effort teardown inside parentT.Cleanup that iterates createdDocIDs and
calls the document deletion API (e.g., docsClient.DeleteDocument or
docsClient.Delete(ctx, docID)) for each ID, ignoring or logging errors but not
failing the test; ensure you reference the same client/ctx variables used
elsewhere in the test so Cleanup can delete the remote docs it created.
- Around line 18-22: The test TestDocs_CreateAndFetchWorkflow does not isolate
CLI config and may leak auth/config between runs; fix by setting the environment
variable LARKSUITE_CLI_CONFIG_DIR to a per-test temp dir at the start of
TestDocs_CreateAndFetchWorkflow (use t.Setenv("LARKSUITE_CLI_CONFIG_DIR",
t.TempDir())) so the test uses an isolated config directory for its lifetime.

In `@tests/cli_e2e/docs/docs_update_test.go`:
- Around line 45-47: Replace the no-op parentT.Cleanup stub with a best-effort
teardown that removes or archives any docs created by the test: inside the
parentT.Cleanup(func() { ... }) body iterate over the test's created-doc
identifiers (e.g., createdDocIDs or similar test-scoped slice), call the
appropriate deletion/archive helper on your client (e.g., client.DeleteDoc or
client.ArchiveDoc/TeardownDocs) for each ID, swallow/log but do not return
errors (best-effort), and ensure any client or context variables used in tests
are captured/closed; if teardown is intentionally deferred, remove the stub
instead.
- Around line 18-22: TestDocs_UpdateWorkflow shares CLI config state causing
flakiness; modify the test (TestDocs_UpdateWorkflow) to isolate config by
calling t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) early in the test
setup (e.g., immediately after parentT := t or right after ctx creation) so the
CLI uses a unique temporary config directory for this test only.

In `@tests/cli_e2e/im/chat_workflow_test.go`:
- Line 157: Remove or redact raw chat payloads and share links being printed in
CI logs: stop calling t.Logf with result.Stdout in chat_workflow_test.go (and
the similar log at line 194) and instead either remove the log entirely or log a
sanitized summary (e.g., status/length or a redacted string). Locate the test
helper or variable names result and share_link in the test (e.g., the
t.Logf("chats get result: %s", result.Stdout) call) and replace it with a
non-sensitive assertion or a redacted/summarized log entry that strips
identifiers and URLs before printing.
- Around line 17-20: In TestIM_ChatCreateSendWorkflow add isolation for CLI
config state by calling t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) near
the top (e.g., immediately after parentT := t) so each top-level e2e test uses
its own config dir; apply the same change to the other top-level test functions
in this file (the ones that start other workflows) to prevent auth/config state
bleeding between runs.

In `@tests/cli_e2e/README.md`:
- Line 115: Replace the hardcoded absolute path in the PLAYWRIGHT_STORAGE_STATE
export with a relative path or placeholder; update the export line that sets
PLAYWRIGHT_STORAGE_STATE so it points to a repo-relative location (e.g., a
./tests/... path) or a placeholder like ${PLAYWRIGHT_STORAGE_STATE_PATH} so
other contributors can set their own value, and document the expected default in
the README.

---

Nitpick comments:
In @.gitignore:
- Line 37: The .gitignore currently has a root-only entry "app.log"; to also
ignore app.log files generated in subdirectories (e.g., during E2E tests) add a
recursive pattern such as "**/app.log" (or replace "app.log" with "**/app.log")
so that the ignore applies to all directories; update the .gitignore entry
referencing the existing "app.log" line accordingly.

In `@tests/cli_e2e/base/base_basic_workflow_test.go`:
- Around line 66-67: The assertion only checks gjson.Get(result.Stdout,
"data.table.id") and should also accept the alternative "data.table.table_id"
like the createTable helper does; update the check that compares to tableID to
read either gjson.Get(result.Stdout, "data.table.id").String() or
gjson.Get(result.Stdout, "data.table.table_id").String() (fall back to the
latter when the former is empty) while keeping the tableName assertion unchanged
so tests pass regardless of which field the API returns.

In `@tests/cli_e2e/base/helpers_test.go`:
- Around line 510-522: writeTempAttachment currently creates the temp file in
the current working directory which can conflict across tests; change it to use
t.TempDir() to create an isolated directory, build the file path with
filepath.Join(tempDir, "attachment-"+testSuffix()+".txt"), write the file via
os.WriteFile, and remove the manual t.Cleanup removal (t.TempDir handles
cleanup) so the function returns "./"+filepath.Base(path) or simply the path as
needed by callers; update references to writeTempAttachment if they rely on
cwd-relative paths.
- Around line 491-508: createWorkflow currently doesn't register cleanup so
created workflows linger; change the helper to accept a parentT *testing.T (or
another *testing.T parameter) and register parentT.Cleanup to delete the
workflow after the test completes. After obtaining workflowID in createWorkflow,
invoke parentT.Cleanup with a function that calls clie2e.RunCmd (similar to the
create call) to run "base +workflow-delete --base-token <baseToken>
--workflow-id <workflowID>" (and handle non-zero exit like skipIfBaseUnavailable
if necessary), ensuring deletion is attempted but does not fail the test if the
base is unavailable.

In `@tests/cli_e2e/calendar/calendar_create_event_test.go`:
- Around line 94-107: The "verify delete acknowledged" subtest currently calls
clie2e.RunCmd in tests/cli_e2e/calendar/calendar_create_event_test.go and then
discards the response (result) which means it verifies nothing; update the
subtest (named "verify delete acknowledged") to either remove the test entirely
or assert a meaningful outcome: for example, call clie2e.RunCmd with Args
{"calendar","events","get"} and Params {"calendar_id": calendarID, "event_id":
eventID} and then assert the expected deletion behavior (e.g., require.Error(t,
err) and check the error indicates NotFound or require.Contains(t,
result.StdErr, "not found") if the CLI returns non-zero output), or add a short
comment explaining that the response is intentionally ignored for eventual
consistency — reference the variables eventID, calendarID and the clie2e.RunCmd
invocation when making the change.

In `@tests/cli_e2e/calendar/helpers_test.go`:
- Around line 39-54: The cleanup callback uses context.Background() which can
hang; change the RunCmd call in the parentT.Cleanup closure to use the timeout
context helper (cleanupContext()) instead of context.Background() so the delete
request uses the 30s timeout—update the clie2e.RunCmd invocation inside the
parentT.Cleanup block (the calendar events delete call that passes
calendar_id/event_id) to call cleanupContext() like other helpers (see
base/helpers_test.go) so the cleanup will time out reliably.

In `@tests/cli_e2e/drive/helpers_test.go`:
- Around line 18-19: The constant testFileDir is declared but not used; replace
the duplicated inline string literals with this constant: update any occurrences
of "tests/cli_e2e/drive/testfiles" (notably inside the function importTestDoc
and the other test helper) to use testFileDir instead so the path is
centralized; keep the const declaration as-is and ensure importTestDoc and any
other helpers reference testFileDir.
- Around line 27-28: The calls to os.MkdirAll (creating testDir) ignore errors
and can hide CI/filesystem failures; update both occurrences to check the
returned error and fail the test on error (use t.Fatalf or t.Fatal with a
message including the error) so the test explicitly handles and reports
permission or filesystem issues when creating the test directory referenced by
testDir and the similar call later in the file.

In `@tests/cli_e2e/im/chat_workflow_test.go`:
- Around line 115-137: The tests "update chat name" and "update chat
description" only assert success envelopes; after calling clie2e.RunCmd with
Args ["im", "+chat-update", "--chat-id", chatID, "--name", updatedName] and
similarly for description, add a follow-up clie2e.RunCmd invocation that runs
["im", "chats", "get", "--chat-id", chatID] and assert the returned stdout
contains the updated value (updatedName or updatedDescription) and still exits
0; update the subtests to call RunCmd for "im chats get", parse/inspect stdout,
and use require/assert helpers to validate the persisted field so the mutation
is actually verified.

In `@tests/cli_e2e/im/helpers_test.go`:
- Around line 69-86: The helper functions declare an unused parentT
parameter—remove the unused parentT from the signatures of sendMessage,
sendMarkdown, sendImage, replyMessage, and replyInThread and update all call
sites to drop that argument; ensure you only change the function signatures and
their invocations (e.g., calls to sendMessage(ctx, chatID, text) instead of
sendMessage(parentT, ctx, chatID, text)) so tests compile and no unused
parameter warnings remain.

In `@tests/cli_e2e/sheets/sheets_filter_workflow_test.go`:
- Around line 175-268: The local variable parentT in TestSheets_FindWorkflow is
unused; remove the parentT declaration and the empty parentT.Cleanup callback
inside the "create spreadsheet" t.Run block. Edit the TestSheets_FindWorkflow
function to delete the line creating parentT and the parentT.Cleanup(...) block
so only the existing t and its real cleanup (ctx cancel) remain; no other logic
changes to the "create spreadsheet" subtest are required.

In `@tests/cli_e2e/wiki/helpers_test.go`:
- Around line 57-73: The helper createWikiNode currently returns a created node
but never cleans it up; change its signature to accept parentT *testing.T (e.g.,
createWikiNode(parentT *testing.T, t *testing.T, ctx context.Context, req
clie2e.Request) or swap params to match callers), and after obtaining node (from
wikiJSONPayload and gjson.Get "data.node") register parentT.Cleanup to run a
clie2e.RunCmd delete request that deletes the node (use the node ID from the
node result); keep existing skipIfWikiUnavailable and result assertions
unchanged so deletion only registers when creation succeeded. Ensure callers are
updated to pass parentT and that the cleanup invokes the same CLI path used
elsewhere for removing wiki nodes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 80af146c-8b4e-43ee-b4c1-21da61f681f2

📥 Commits

Reviewing files that changed from the base of the PR and between 344ff88 and 0a268fd.

📒 Files selected for processing (34)
  • .gitignore
  • tests/cli_e2e/README.md
  • tests/cli_e2e/base/base_basic_workflow_test.go
  • tests/cli_e2e/base/base_role_workflow_test.go
  • tests/cli_e2e/base/helpers_test.go
  • tests/cli_e2e/calendar/calendar_create_event_test.go
  • tests/cli_e2e/calendar/calendar_find_meeting_time_test.go
  • tests/cli_e2e/calendar/calendar_manage_calendar_test.go
  • tests/cli_e2e/calendar/calendar_view_agenda_test.go
  • tests/cli_e2e/calendar/helpers_test.go
  • tests/cli_e2e/cli-e2e-testcase-writer/SKILL.md
  • tests/cli_e2e/contact/contact_shortcut_test.go
  • tests/cli_e2e/coverage.md
  • tests/cli_e2e/demo/coverage.md
  • tests/cli_e2e/docs/docs_create_fetch_test.go
  • tests/cli_e2e/docs/docs_update_test.go
  • tests/cli_e2e/drive/drive_files_workflow_test.go
  • tests/cli_e2e/drive/drive_move_workflow_test.go
  • tests/cli_e2e/drive/drive_permission_user_workflow_test.go
  • tests/cli_e2e/drive/helpers_test.go
  • tests/cli_e2e/im/chat_workflow_test.go
  • tests/cli_e2e/im/helpers_test.go
  • tests/cli_e2e/im/message_workflow_test.go
  • tests/cli_e2e/sheets/sheets_crud_workflow_test.go
  • tests/cli_e2e/sheets/sheets_filter_workflow_test.go
  • tests/cli_e2e/task/coverage.md
  • tests/cli_e2e/task/task_comment_workflow_test.go
  • tests/cli_e2e/task/task_get_my_tasks_user_test.go
  • tests/cli_e2e/task/task_reminder_workflow_test.go
  • tests/cli_e2e/task/task_status_workflow_test.go
  • tests/cli_e2e/task/tasklist_add_task_workflow_test.go
  • tests/cli_e2e/task/tasklist_workflow_test.go
  • tests/cli_e2e/wiki/helpers_test.go
  • tests/cli_e2e/wiki/wiki_workflow_test.go

Comment on lines +17 to +20
func TestBase_RoleWorkflow(t *testing.T) {
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
t.Cleanup(cancel)
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 per test run.

This test does not isolate LARKSUITE_CLI_CONFIG_DIR, so state can leak across parallel/previous runs and make e2e results flaky.

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

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_role_workflow_test.go` around lines 17 - 20, The test
TestBase_RoleWorkflow currently doesn't isolate CLI config and can leak state;
update the test setup to set a unique config directory by calling
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) early in TestBase_RoleWorkflow
(before any CLI operations or goroutines) so each run uses an isolated config
dir; ensure this is added alongside the existing context/cancel setup using the
test's t variable.

result.AssertStdoutStatus(t, true)

roleName := "Reviewer-" + testSuffix()
roleID := createRole(t, parentT, ctx, baseToken, `{"role_name":"`+roleName+`","role_type":"custom_role"}`)
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

Avoid deleting the same role twice (workflow delete + helper cleanup).

createRole(...) already registers a parentT.Cleanup delete in tests/cli_e2e/base/helpers_test.go:428-489. Running +role-delete again in this test can cause cleanup failure/noise when cleanup executes.

Consider either:

  1. keep the explicit delete subtest and create the role without auto-cleanup helper, or
  2. keep helper cleanup and drop the explicit delete subtest.

Also applies to: 118-129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/base/base_role_workflow_test.go` at line 35, The test creates a
role via createRole(...) which already registers a parentT.Cleanup delete (see
parentT.Cleanup in helpers_test.go), so avoid double-deleting by either removing
the explicit "+role-delete" subtest or changing the create call to a non-cleanup
variant; locate the role creation line where roleID := createRole(t, parentT,
ctx, baseToken, `{"role_name":"`+roleName+`","role_type":"custom_role"}`) and
either (A) delete the subtest that calls the workflow "+role-delete" later in
this test, or (B) replace createRole with a helper that does not register
parentT.Cleanup so the explicit delete remains — apply the same change for the
other occurrence around lines 118-129.

Comment on lines +16 to +19
func TestCalendar_FindMeetingTime(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)

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 per test to avoid cross-test contamination.

This test invokes CLI commands without setting a test-local config dir, which can leak shared state across runs.

🔧 Proposed fix
 func TestCalendar_FindMeetingTime(t *testing.T) {
+	t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
 	ctx, cancel := context.WithTimeout(context.Background(), 2*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()).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/calendar/calendar_find_meeting_time_test.go` around lines 16 -
19, In TestCalendar_FindMeetingTime, the CLI config state isn't isolated and can
leak between tests; set a test-local config dir by calling
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) near the start of the test
(after ctx/cancel creation) so all CLI invocations in
TestCalendar_FindMeetingTime use an isolated config directory.

Comment on lines +16 to +18
func TestCalendar_ViewAgenda(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)
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

Add per-test config directory isolation before running CLI commands.

Without a test-local config dir, this test may read/write shared user state and become flaky in parallel or CI runs.

🔧 Proposed fix
 func TestCalendar_ViewAgenda(t *testing.T) {
+	t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
 	ctx, cancel := context.WithTimeout(context.Background(), 2*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 TestCalendar_ViewAgenda(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)
func TestCalendar_ViewAgenda(t *testing.T) {
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/calendar/calendar_view_agenda_test.go` around lines 16 - 18, In
TestCalendar_ViewAgenda, isolate per-test config state by setting the
LARKSUITE_CLI_CONFIG_DIR to a temp directory before any CLI invocation: call
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) at the start of the
TestCalendar_ViewAgenda function (after ctx creation is fine) so the test
reads/writes only test-local config and avoids shared state in parallel/CI runs.


Then assert the business fields with `gjson`.
- Mark each command `shortcut` or `api`.
- Write testcase entries in `go test -run` friendly form.
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 | 🟡 Minor

Use hyphenated phrasing for readability.

Change “go test -run friendly form” to “go test -run-friendly form”.

🧰 Tools
🪛 LanguageTool

[grammar] ~107-~107: Use a hyphen to join words.
Context: ...Write testcase entries in go test -run friendly form. - Commands only exercised...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/cli-e2e-testcase-writer/SKILL.md` at line 107, Update the
phrasing to use hyphenation for readability: replace the phrase "go test -run
friendly form" with "`go test -run`-friendly form" in the SKILL.md entry (the
line that currently reads "Write testcase entries in `go test -run` friendly
form.").

Comment on lines +18 to +22
func TestDocs_UpdateWorkflow(t *testing.T) {
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)

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 test.

This test currently shares config state with other tests/processes, which can make e2e runs flaky.

🔧 Proposed fix
 func TestDocs_UpdateWorkflow(t *testing.T) {
+	t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
 	parentT := t
 	ctx, cancel := context.WithTimeout(context.Background(), 2*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 TestDocs_UpdateWorkflow(t *testing.T) {
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)
func TestDocs_UpdateWorkflow(t *testing.T) {
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/docs/docs_update_test.go` around lines 18 - 22,
TestDocs_UpdateWorkflow shares CLI config state causing flakiness; modify the
test (TestDocs_UpdateWorkflow) to isolate config by calling
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) early in the test setup (e.g.,
immediately after parentT := t or right after ctx creation) so the CLI uses a
unique temporary config directory for this test only.

Comment on lines +45 to +47
parentT.Cleanup(func() {
// best-effort cleanup
})
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

Replace the no-op cleanup with real teardown.

The cleanup registered on Line 45 does nothing, so created docs are left behind after test runs. Please implement actual best-effort deletion/archive there (or remove the stub if teardown is intentionally deferred).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/docs/docs_update_test.go` around lines 45 - 47, Replace the
no-op parentT.Cleanup stub with a best-effort teardown that removes or archives
any docs created by the test: inside the parentT.Cleanup(func() { ... }) body
iterate over the test's created-doc identifiers (e.g., createdDocIDs or similar
test-scoped slice), call the appropriate deletion/archive helper on your client
(e.g., client.DeleteDoc or client.ArchiveDoc/TeardownDocs) for each ID,
swallow/log but do not return errors (best-effort), and ensure any client or
context variables used in tests are captured/closed; if teardown is
intentionally deferred, remove the stub instead.

Comment on lines +17 to +20
func TestIM_ChatCreateSendWorkflow(t *testing.T) {
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)
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

Set an isolated config dir in each top-level e2e test.

These tests don’t isolate LARKSUITE_CLI_CONFIG_DIR, so CLI auth/config state can bleed between runs.

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

Also applies to: 62-63, 105-107, 143-145, 172-174

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/im/chat_workflow_test.go` around lines 17 - 20, In
TestIM_ChatCreateSendWorkflow add isolation for CLI config state by calling
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) near the top (e.g.,
immediately after parentT := t) so each top-level e2e test uses its own config
dir; apply the same change to the other top-level test functions in this file
(the ones that start other workflows) to prevent auth/config state bleeding
between runs.

Comment thread tests/cli_e2e/im/chat_workflow_test.go
Comment thread tests/cli_e2e/README.md
Then run E2E with:

```bash
export PLAYWRIGHT_STORAGE_STATE=/Users/bytedance/cli/tests/cli_e2e/browser/.auth/state.json
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 | 🟡 Minor

Replace hardcoded absolute path with a relative or placeholder path.

The path /Users/bytedance/cli/tests/cli_e2e/browser/.auth/state.json is user-specific and won't work for other contributors. Consider using a relative path or a placeholder.

📝 Suggested fix
-export PLAYWRIGHT_STORAGE_STATE=/Users/bytedance/cli/tests/cli_e2e/browser/.auth/state.json
+export PLAYWRIGHT_STORAGE_STATE=$(pwd)/tests/cli_e2e/browser/.auth/state.json

Or use a placeholder:

-export PLAYWRIGHT_STORAGE_STATE=/Users/bytedance/cli/tests/cli_e2e/browser/.auth/state.json
+export PLAYWRIGHT_STORAGE_STATE=/path/to/cli/tests/cli_e2e/browser/.auth/state.json
📝 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
export PLAYWRIGHT_STORAGE_STATE=/Users/bytedance/cli/tests/cli_e2e/browser/.auth/state.json
export PLAYWRIGHT_STORAGE_STATE=$(pwd)/tests/cli_e2e/browser/.auth/state.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/cli_e2e/README.md` at line 115, Replace the hardcoded absolute path in
the PLAYWRIGHT_STORAGE_STATE export with a relative path or placeholder; update
the export line that sets PLAYWRIGHT_STORAGE_STATE so it points to a
repo-relative location (e.g., a ./tests/... path) or a placeholder like
${PLAYWRIGHT_STORAGE_STATE_PATH} so other contributors can set their own value,
and document the expected default in the README.

@yxzhaao yxzhaao closed this Apr 10, 2026
@yxzhaao yxzhaao deleted the feat/cli-e2e-tests-for-bot-subset branch April 13, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants