Skip to content
This repository was archived by the owner on Apr 3, 2026. It is now read-only.

Commit 5731c68

Browse files
Fix and unskip some skills E2E tests (github#69)
* Re-enable skills tests for Node * Re-enable them for .NET * Re-enable and fix for Python * Re-enable and fix Go tests * Actually skip * Formatting * More formatting
1 parent 2415f6f commit 5731c68

File tree

6 files changed

+125
-35
lines changed

6 files changed

+125
-35
lines changed

dotnet/test/SkillsTests.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
* Copyright (c) Microsoft Corporation. All rights reserved.
33
*--------------------------------------------------------------------------------------------*/
44

5-
using GitHub.Copilot.SDK.Test.Harness;
65
using Xunit;
76
using Xunit.Abstractions;
87

@@ -11,18 +10,23 @@ namespace GitHub.Copilot.SDK.Test;
1110
public class SkillsTests : E2ETestBase
1211
{
1312
private const string SkillMarker = "PINEAPPLE_COCONUT_42";
14-
private static int _skillDirCounter = 0;
1513

1614
private readonly string _workDir;
1715

1816
public SkillsTests(E2ETestFixture fixture, ITestOutputHelper output) : base(fixture, "skills", output)
1917
{
2018
_workDir = fixture.Ctx.WorkDir;
19+
20+
var skillsDir = Path.Join(_workDir, ".test_skills");
21+
if (Directory.Exists(skillsDir))
22+
{
23+
Directory.Delete(skillsDir, recursive: true);
24+
}
2125
}
2226

2327
private string CreateSkillDir()
2428
{
25-
var skillsDir = Path.Join(_workDir, ".test_skills", $"copilot-skills-test-{++_skillDirCounter}");
29+
var skillsDir = Path.Join(_workDir, ".test_skills");
2630
Directory.CreateDirectory(skillsDir);
2731

2832
// Create a skill subdirectory with SKILL.md
@@ -44,7 +48,7 @@ private string CreateSkillDir()
4448
return skillsDir;
4549
}
4650

47-
[Fact(Skip = "Skills tests temporarily skipped")]
51+
[Fact]
4852
public async Task Should_Load_And_Apply_Skill_From_SkillDirectories()
4953
{
5054
var skillsDir = CreateSkillDir();
@@ -63,7 +67,7 @@ public async Task Should_Load_And_Apply_Skill_From_SkillDirectories()
6367
await session.DisposeAsync();
6468
}
6569

66-
[Fact(Skip = "Skills tests temporarily skipped")]
70+
[Fact]
6771
public async Task Should_Not_Apply_Skill_When_Disabled_Via_DisabledSkills()
6872
{
6973
var skillsDir = CreateSkillDir();
@@ -83,7 +87,7 @@ public async Task Should_Not_Apply_Skill_When_Disabled_Via_DisabledSkills()
8387
await session.DisposeAsync();
8488
}
8589

86-
[Fact(Skip = "Skills tests temporarily skipped")]
90+
[Fact(Skip = "See the big comment around the equivalent test in the Node SDK. Skipped because the feature doesn't work correctly yet.")]
8791
public async Task Should_Apply_Skill_On_Session_Resume_With_SkillDirectories()
8892
{
8993
var skillsDir = CreateSkillDir();

go/e2e/skills_test.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package e2e
22

33
import (
4-
"fmt"
54
"os"
65
"path/filepath"
76
"strings"
@@ -14,11 +13,15 @@ import (
1413

1514
const skillMarker = "PINEAPPLE_COCONUT_42"
1615

17-
var skillDirCounter = 0
16+
func cleanSkillsDir(t *testing.T, workDir string) {
17+
skillsDir := filepath.Join(workDir, ".test_skills")
18+
if err := os.RemoveAll(skillsDir); err != nil {
19+
t.Fatalf("Failed to clean skills directory: %v", err)
20+
}
21+
}
1822

1923
func createTestSkillDir(t *testing.T, workDir string, marker string) string {
20-
skillDirCounter++
21-
skillsDir := filepath.Join(workDir, ".test_skills", fmt.Sprintf("copilot-skills-test-%d", skillDirCounter))
24+
skillsDir := filepath.Join(workDir, ".test_skills")
2225
if err := os.MkdirAll(skillsDir, 0755); err != nil {
2326
t.Fatalf("Failed to create skills directory: %v", err)
2427
}
@@ -44,14 +47,14 @@ IMPORTANT: You MUST include the exact text "` + marker + `" somewhere in EVERY r
4447
return skillsDir
4548
}
4649

47-
func TestSkillBehavior(t *testing.T) {
48-
t.Skip("Skills tests temporarily skipped")
50+
func TestSkills(t *testing.T) {
4951
ctx := testharness.NewTestContext(t)
5052
client := ctx.NewClient()
5153
t.Cleanup(func() { client.ForceStop() })
5254

53-
t.Run("load and apply skill from skillDirectories", func(t *testing.T) {
55+
t.Run("should load and apply skill from skillDirectories", func(t *testing.T) {
5456
ctx.ConfigureForTest(t)
57+
cleanSkillsDir(t, ctx.WorkDir)
5558
skillsDir := createTestSkillDir(t, ctx.WorkDir, skillMarker)
5659

5760
session, err := client.CreateSession(&copilot.SessionConfig{
@@ -76,8 +79,9 @@ func TestSkillBehavior(t *testing.T) {
7679
session.Destroy()
7780
})
7881

79-
t.Run("not apply skill when disabled via disabledSkills", func(t *testing.T) {
82+
t.Run("should not apply skill when disabled via disabledSkills", func(t *testing.T) {
8083
ctx.ConfigureForTest(t)
84+
cleanSkillsDir(t, ctx.WorkDir)
8185
skillsDir := createTestSkillDir(t, ctx.WorkDir, skillMarker)
8286

8387
session, err := client.CreateSession(&copilot.SessionConfig{
@@ -103,8 +107,10 @@ func TestSkillBehavior(t *testing.T) {
103107
session.Destroy()
104108
})
105109

106-
t.Run("apply skill on session resume with skillDirectories", func(t *testing.T) {
110+
t.Run("should apply skill on session resume with skillDirectories", func(t *testing.T) {
111+
t.Skip("See the big comment around the equivalent test in the Node SDK. Skipped because the feature doesn't work correctly yet.")
107112
ctx.ConfigureForTest(t)
113+
cleanSkillsDir(t, ctx.WorkDir)
108114
skillsDir := createTestSkillDir(t, ctx.WorkDir, skillMarker)
109115

110116
// Create a session without skills first

nodejs/test/e2e/skills.test.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@
44

55
import * as fs from "fs";
66
import * as path from "path";
7-
import { describe, expect, it } from "vitest";
7+
import { beforeEach, describe, expect, it } from "vitest";
88
import { createSdkTestContext } from "./harness/sdkTestContext.js";
99

10-
describe.skip("Skills Configuration", async () => {
10+
describe("Skills Configuration", async () => {
1111
const { copilotClient: client, workDir } = await createSdkTestContext({ logLevel: "debug" });
1212
const SKILL_MARKER = "PINEAPPLE_COCONUT_42";
13-
let skillDirCounter = 0;
13+
const skillsDir = path.join(workDir, ".test_skills");
14+
15+
beforeEach(() => {
16+
// Ensure we start fresh each time
17+
if (fs.existsSync(skillsDir)) {
18+
fs.rmSync(skillsDir, { recursive: true, force: true });
19+
}
20+
});
1421

1522
function createSkillDir(): string {
16-
const skillsDir = path.join(
17-
workDir,
18-
".test_skills",
19-
`copilot-skills-test-${++skillDirCounter}`
20-
);
2123
fs.mkdirSync(skillsDir, { recursive: true });
2224

2325
// Create a skill subdirectory with SKILL.md
@@ -76,7 +78,18 @@ IMPORTANT: You MUST include the exact text "${SKILL_MARKER}" somewhere in EVERY
7678
await session.destroy();
7779
});
7880

79-
it("should apply skill on session resume with skillDirectories", async () => {
81+
// Skipped because the underlying feature doesn't work correctly yet.
82+
// - If this test is run during the same run as other tests in this file (sharing the same Client instance),
83+
// or if it already has a snapshot of the traffic from a passing run, it passes
84+
// - But if you delete the snapshot for this test and then run it alone, it fails
85+
// Be careful not to unskip this test just because it passes when run alongside others. It needs to pass when
86+
// run alone and without any prior snapshot.
87+
// It's likely there's an underlying issue either with session resumption in all the client SDKs, or in CLI with
88+
// how skills are applied on session resume.
89+
// Also, if this test runs FIRST and then the "should load and apply skill from skillDirectories" test runs second
90+
// within the same run (i.e., sharing the same Client instance), then the second test fails too. There's definitely
91+
// some state being shared or cached incorrectly.
92+
it.skip("should apply skill on session resume with skillDirectories", async () => {
8093
const skillsDir = createSkillDir();
8194

8295
// Create a session without skills first

python/e2e/test_skills.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import os
6+
import shutil
67

78
import pytest
89

@@ -12,14 +13,19 @@
1213

1314
SKILL_MARKER = "PINEAPPLE_COCONUT_42"
1415

15-
_skill_dir_counter = 0
16+
17+
@pytest.fixture(autouse=True)
18+
def clean_skills_dir(ctx: E2ETestContext):
19+
"""Ensure we start fresh each time"""
20+
skills_dir = os.path.join(ctx.work_dir, ".test_skills")
21+
if os.path.exists(skills_dir):
22+
shutil.rmtree(skills_dir)
23+
yield
1624

1725

1826
def create_skill_dir(work_dir: str) -> str:
1927
"""Create a skills directory in the working directory"""
20-
global _skill_dir_counter
21-
_skill_dir_counter += 1
22-
skills_dir = os.path.join(work_dir, ".test_skills", f"copilot-skills-test-{_skill_dir_counter}")
28+
skills_dir = os.path.join(work_dir, ".test_skills")
2329
os.makedirs(skills_dir, exist_ok=True)
2430

2531
# Create a skill subdirectory with SKILL.md
@@ -34,18 +40,17 @@ def create_skill_dir(work_dir: str) -> str:
3440
3541
# Test Skill Instructions
3642
37-
IMPORTANT: You MUST include the exact text "{SKILL_MARKER}" somewhere in EVERY response you give.
43+
IMPORTANT: You MUST include the exact text "{SKILL_MARKER}" somewhere in EVERY response you give. \
3844
This is a mandatory requirement. Include it naturally in your response.
39-
"""
40-
with open(os.path.join(skill_subdir, "SKILL.md"), "w") as f:
45+
""".replace("\r", "")
46+
with open(os.path.join(skill_subdir, "SKILL.md"), "w", newline="\n") as f:
4147
f.write(skill_content)
4248

4349
return skills_dir
4450

4551

46-
@pytest.mark.skip(reason="Skills tests temporarily skipped")
4752
class TestSkillBehavior:
48-
async def test_load_and_apply_skill_from_skill_directories(self, ctx: E2ETestContext):
53+
async def test_should_load_and_apply_skill_from_skilldirectories(self, ctx: E2ETestContext):
4954
"""Test that skills are loaded and applied from skillDirectories"""
5055
skills_dir = create_skill_dir(ctx.work_dir)
5156
session = await ctx.client.create_session({"skill_directories": [skills_dir]})
@@ -59,7 +64,9 @@ async def test_load_and_apply_skill_from_skill_directories(self, ctx: E2ETestCon
5964

6065
await session.destroy()
6166

62-
async def test_not_apply_skill_when_disabled_via_disabled_skills(self, ctx: E2ETestContext):
67+
async def test_should_not_apply_skill_when_disabled_via_disabledskills(
68+
self, ctx: E2ETestContext
69+
):
6370
"""Test that disabledSkills prevents skill from being applied"""
6471
skills_dir = create_skill_dir(ctx.work_dir)
6572
session = await ctx.client.create_session(
@@ -75,7 +82,13 @@ async def test_not_apply_skill_when_disabled_via_disabled_skills(self, ctx: E2ET
7582

7683
await session.destroy()
7784

78-
async def test_apply_skill_on_session_resume_with_skill_directories(self, ctx: E2ETestContext):
85+
@pytest.mark.skip(
86+
reason="See the big comment around the equivalent test in the Node SDK. "
87+
"Skipped because the feature doesn't work correctly yet."
88+
)
89+
async def test_should_apply_skill_on_session_resume_with_skilldirectories(
90+
self, ctx: E2ETestContext
91+
):
7992
"""Test that skills are applied when added on session resume"""
8093
skills_dir = create_skill_dir(ctx.work_dir)
8194

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
models:
2+
- claude-sonnet-4.5
3+
conversations:
4+
- messages:
5+
- role: system
6+
content: ${system}
7+
- role: user
8+
content: Say hello briefly using the test skill.
9+
- role: assistant
10+
tool_calls:
11+
- id: toolcall_0
12+
type: function
13+
function:
14+
name: skill
15+
arguments: '{"skill":"test-skill"}'
16+
- role: tool
17+
tool_call_id: toolcall_0
18+
content: Skill "test-skill" loaded successfully. Follow the instructions in the skill context.
19+
- role: user
20+
content: >-
21+
<skill-context name="test-skill">
22+
23+
Base directory for this skill: ${workdir}/.test_skills/test-skill
24+
25+
26+
---
27+
28+
name: test-skill
29+
30+
description: A test skill that adds a marker to responses
31+
32+
---
33+
34+
35+
# Test Skill Instructions
36+
37+
38+
IMPORTANT: You MUST include the exact text "PINEAPPLE_COCONUT_42" somewhere in EVERY response you give. This
39+
is a mandatory requirement. Include it naturally in your response.
40+
41+
42+
</skill-context>
43+
- role: assistant
44+
content: Hello! PINEAPPLE_COCONUT_42 - Ready to help you with your tasks.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
models:
2+
- claude-sonnet-4.5
3+
conversations:
4+
- messages:
5+
- role: system
6+
content: ${system}
7+
- role: user
8+
content: Say hello briefly using the test skill.
9+
- role: assistant
10+
content: Hello! I'm GitHub Copilot CLI, ready to help you with your software engineering tasks.

0 commit comments

Comments
 (0)