Skip to content

Commit ae7ad11

Browse files
cpb8010Copilot
andauthored
feat: setup session list with tests (#261)
* feat: setup session list with tests have partially passing tests * feat: setup tests they run and it's hard to tell if the tests are broken or if the list table is broken in actually getting sessions * fix: use correct types still filtering * fix: passing tests just required changing how it looks for sessions also cleanup * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: linting updates from code review * chore: more linting fixes ran non-nightly before, so this is the diff * fix: more clippy warnings * fix: update ci for rn crossing fingers on this one * fix: another xcode attempt * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: more code cleanup * fix: more test comment cleanup * chore: more code cleanup from comments * fix: address code review comments error improvements * fix: more swift sdk updates * fix: another swift attempt at ci I can't even test this locally, so this is guess and check * fix: more swift ci fixes debugging via ci is my favorite --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 08f15c5 commit ae7ad11

File tree

16 files changed

+833
-122
lines changed

16 files changed

+833
-122
lines changed

.github/workflows/ci-swift.yml

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ permissions:
55
on:
66
push:
77
paths:
8-
- 'packages/contracts/**'
9-
- 'packages/sdk-platforms/rust/**'
10-
- 'packages/sdk-platforms/swift/**'
11-
- '.github/workflows/ci-swift.yml'
8+
- "packages/contracts/**"
9+
- "packages/sdk-platforms/rust/**"
10+
- "packages/sdk-platforms/swift/**"
11+
- ".github/workflows/ci-swift.yml"
1212
pull_request:
1313
paths:
14-
- 'packages/contracts/**'
15-
- 'packages/sdk-platforms/rust/**'
16-
- 'packages/sdk-platforms/swift/**'
17-
- '.github/workflows/ci-swift.yml'
14+
- "packages/contracts/**"
15+
- "packages/sdk-platforms/rust/**"
16+
- "packages/sdk-platforms/swift/**"
17+
- ".github/workflows/ci-swift.yml"
1818

1919
jobs:
2020
swift-sdk:
@@ -28,7 +28,7 @@ jobs:
2828
- uses: actions/checkout@v4
2929
with:
3030
submodules: recursive
31-
31+
3232
- name: Run sccache-cache
3333
uses: mozilla-actions/sccache-action@v0.0.4
3434

@@ -83,22 +83,60 @@ jobs:
8383
- name: Select Xcode 16.3
8484
run: sudo xcode-select -s /Applications/Xcode_16.3.app
8585

86-
- name: Select Simulator
86+
- name: Create and Select Simulator
8787
run: |
88-
UDID=$(xcrun simctl list devices | awk '/-- iOS 18.4 --/{flag=1; next} /--/{flag=0} flag' | grep "iPhone 16 Pro" | awk -F '[()]' '{print $2}' | head -1)
89-
if [ -z "$UDID" ]; then
90-
echo "Simulator not found!" >&2
88+
# List available runtimes before installation
89+
echo "Available runtimes before installation:"
90+
xcrun simctl list runtimes
91+
92+
# Download iOS platform
93+
echo "Downloading iOS platform..."
94+
xcodebuild -downloadPlatform iOS
95+
96+
# Wait for download to complete
97+
sleep 10
98+
99+
# List available runtimes after installation
100+
echo "Available runtimes after installation:"
101+
xcrun simctl list runtimes
102+
103+
# Get the latest iOS runtime
104+
IOS_RUNTIME=$(xcrun simctl list runtimes | grep "iOS" | tail -1 | awk '{for(i=1;i<=NF;i++) if($i ~ /^com\.apple\.CoreSimulator\.SimRuntime/) print $i}' | tr -d ')')
105+
106+
if [ -z "$IOS_RUNTIME" ]; then
107+
echo "ERROR: No iOS runtime found"
108+
xcrun simctl list runtimes
91109
exit 1
92110
fi
93-
echo "Simulator UDID: $UDID"
111+
112+
echo "Using iOS runtime: $IOS_RUNTIME"
113+
114+
# List available device types
115+
echo "Available device types:"
116+
xcrun simctl list devicetypes | grep iPhone
117+
118+
# Create a new iPhone simulator
119+
UDID=$(xcrun simctl create "iPhone-CI" "com.apple.CoreSimulator.SimDeviceType.iPhone-15" "$IOS_RUNTIME")
120+
echo "Created simulator with UDID: $UDID"
121+
122+
# Boot the simulator
123+
xcrun simctl boot "$UDID"
124+
echo "Booted simulator"
125+
126+
# Verify simulator is booted
127+
xcrun simctl list devices | grep "$UDID"
128+
129+
# Wait for simulator to be ready
130+
sleep 5
131+
94132
echo "SIMULATOR_UDID=$UDID" >> $GITHUB_ENV
95-
133+
96134
- name: Install swiftformat
97135
run: brew install swiftformat
98136

99137
- name: Build bindings
100138
run: sh packages/sdk-platforms/rust/zksync-sso/crates/ffi/build-swift-framework-ios-ci.sh
101-
139+
102140
- name: Build & test Swift SDK
103141
run: |
104142
xcodebuild test \

examples/demo-app/tests/create-account.spec.ts

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,178 @@ test("Create account with session, create session via paymaster, and send ETH",
460460

461461
console.log("Session created successfully with balance:", sessionStartBalance, "ETH");
462462
});
463+
464+
test("Create session and verify it appears in auth-server sessions list", async ({ page }) => {
465+
test.setTimeout(120000);
466+
console.log("\n=== Session Display in Sessions List Test ===\n");
467+
468+
// Step 1: Create account
469+
console.log("Step 1: Creating account...");
470+
await page.getByRole("button", { name: "Connect", exact: true }).click();
471+
await page.waitForTimeout(2000);
472+
473+
const popup = page.context().pages()[1];
474+
await expect(popup.getByText("Connect to")).toBeVisible();
475+
476+
// Setup WebAuthn
477+
const client = await popup.context().newCDPSession(popup);
478+
await client.send("WebAuthn.enable");
479+
let newCredential: WebAuthnCredential | null = null;
480+
client.on("WebAuthn.credentialAdded", (credentialAdded) => {
481+
newCredential = credentialAdded.credential;
482+
});
483+
await client.send("WebAuthn.addVirtualAuthenticator", {
484+
options: {
485+
protocol: "ctap2",
486+
transport: "usb",
487+
hasResidentKey: true,
488+
hasUserVerification: true,
489+
isUserVerified: true,
490+
automaticPresenceSimulation: true,
491+
},
492+
});
493+
494+
// Complete signup
495+
await popup.getByTestId("signup").click();
496+
await expect(popup.getByText("Connect to ZKsync SSO Demo")).toBeVisible();
497+
await popup.getByTestId("connect").click();
498+
await page.waitForTimeout(2000);
499+
await expect(page.getByText("Disconnect")).toBeVisible();
500+
501+
// Capture the account address from the page
502+
const demoPageContent = await page.textContent("body");
503+
const accountMatch = demoPageContent?.match(/0x[a-fA-F0-9]{40}/);
504+
const demoAccountAddress = accountMatch ? accountMatch[0] : "unknown";
505+
console.log(`✓ Account created: ${demoAccountAddress}`);
506+
507+
// Step 2: Create session
508+
console.log("\nStep 2: Creating session...");
509+
await page.getByRole("button", { name: "Disconnect", exact: true }).click();
510+
await expect(page.getByRole("button", { name: "Connect with Session", exact: true })).toBeVisible();
511+
await page.getByRole("button", { name: "Connect with Session", exact: true }).click();
512+
await page.waitForTimeout(2000);
513+
514+
const sessionPopup = page.context().pages()[1];
515+
await expect(sessionPopup.getByText("Act on your behalf")).toBeVisible();
516+
517+
// Setup WebAuthn with existing credential
518+
const sessionClient = await sessionPopup.context().newCDPSession(sessionPopup);
519+
await sessionClient.send("WebAuthn.enable");
520+
const sessionAuthenticator = await sessionClient.send("WebAuthn.addVirtualAuthenticator", {
521+
options: {
522+
protocol: "ctap2",
523+
transport: "usb",
524+
hasResidentKey: true,
525+
hasUserVerification: true,
526+
isUserVerified: true,
527+
automaticPresenceSimulation: true,
528+
},
529+
});
530+
await expect(newCredential).not.toBeNull();
531+
await sessionClient.send("WebAuthn.addCredential", {
532+
authenticatorId: sessionAuthenticator.authenticatorId,
533+
credential: newCredential!,
534+
});
535+
536+
// Authorize session
537+
await expect(sessionPopup.getByText("Authorize ZKsync SSO Demo")).toBeVisible();
538+
await sessionPopup.getByTestId("connect").click();
539+
await page.waitForTimeout(3000);
540+
await expect(page.getByText("Disconnect")).toBeVisible();
541+
console.log("✓ Session created");
542+
543+
// Step 3: Navigate to auth-server sessions page to verify
544+
console.log("\nStep 3: Verifying session appears in auth-server...");
545+
546+
const authPage = await page.context().newPage();
547+
await authPage.goto("http://localhost:3002");
548+
await authPage.waitForTimeout(1000);
549+
550+
// Check if logged in
551+
const isLoggedIn = await authPage.locator("[data-testid='account-address']").isVisible({ timeout: 2000 }).catch(() => false);
552+
553+
if (!isLoggedIn) {
554+
console.log("Logging into auth-server...");
555+
// Already on auth-server homepage, just click login
556+
await authPage.getByTestId("login").click();
557+
await authPage.waitForTimeout(1000);
558+
559+
// Setup WebAuthn for login
560+
const authClient = await authPage.context().newCDPSession(authPage);
561+
await authClient.send("WebAuthn.enable");
562+
const authAuthenticator = await authClient.send("WebAuthn.addVirtualAuthenticator", {
563+
options: {
564+
protocol: "ctap2",
565+
transport: "usb",
566+
hasResidentKey: true,
567+
hasUserVerification: true,
568+
isUserVerified: true,
569+
automaticPresenceSimulation: true,
570+
},
571+
});
572+
await authClient.send("WebAuthn.addCredential", {
573+
authenticatorId: authAuthenticator.authenticatorId,
574+
credential: newCredential!,
575+
});
576+
577+
await authPage.waitForURL("**/dashboard", { timeout: 15000 });
578+
console.log("✓ Logged into auth-server");
579+
}
580+
581+
// Navigate to sessions page
582+
await authPage.goto("http://localhost:3002/dashboard/sessions");
583+
await authPage.waitForLoadState("domcontentloaded");
584+
585+
// Listen for console logs from the sessions page
586+
authPage.on("console", (msg) => {
587+
if (msg.text().includes("[sessions.vue]")) {
588+
console.log(` Auth-server: ${msg.text()}`);
589+
}
590+
});
591+
592+
console.log("✓ Navigated to sessions page");
593+
console.log(` Demo account (created session): ${demoAccountAddress}`);
594+
595+
// Verify sessions page content
596+
const header = authPage.locator("header").getByText("Sessions");
597+
await expect(header).toBeVisible();
598+
console.log("✓ Sessions page loaded");
599+
600+
// Wait for sessions data to load - look for either session rows or the table/list container
601+
// The sessions are loaded via WASM asynchronously, so we need to wait
602+
try {
603+
// Wait for the sessions list container or session rows to appear
604+
await authPage.waitForSelector("table tbody tr, [role='list'] > div, [data-testid*='session']", {
605+
timeout: 15000,
606+
state: "attached",
607+
});
608+
console.log("✓ Sessions data container loaded");
609+
} catch (e) {
610+
console.log("⚠ No sessions container appeared within 15s", e);
611+
}
612+
613+
// Additional wait to ensure console logs are captured
614+
await authPage.waitForTimeout(2000);
615+
616+
// Log page content for debugging
617+
const pageContent = await authPage.locator("main").textContent();
618+
console.log(`Page content: ${pageContent?.substring(0, 500)}`);
619+
620+
// Verify at least one session is displayed
621+
// The session rows use class="session-row" in the SessionRow component
622+
const sessionRows = authPage.locator(".session-row");
623+
const sessionCount = await sessionRows.count();
624+
console.log(`Found ${sessionCount} session row(s)`);
625+
626+
expect(sessionCount, "At least one session should be displayed").toBeGreaterThan(0);
627+
console.log(`✓ Found ${sessionCount} session(s) displayed`);
628+
629+
// Verify empty state message is NOT shown
630+
const emptyState = authPage.getByText(/no active sessions/i);
631+
const emptyVisible = await emptyState.isVisible({ timeout: 1000 }).catch(() => false);
632+
expect(emptyVisible, "Empty state should NOT be visible when sessions exist").toBe(false);
633+
console.log("✓ Empty state correctly hidden");
634+
635+
await authPage.close();
636+
console.log("\n=== Session Display Test Complete ===\n");
637+
});

packages/auth-server/components/session/row/Expiry.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ const props = defineProps<{
3838
status: SessionStatus;
3939
isExpired: boolean;
4040
now: number;
41-
createdAt: number;
4241
expiresAt: number;
42+
maxExpiresAt: number;
4343
}>();
4444
4545
const expiresIn = useTimeAgo(props.expiresAt, { showSecond: true, updateInterval: 1000 });
@@ -54,7 +54,10 @@ const sessionExpiry = computed(() => {
5454
});
5555
});
5656
const timeLeft = computed<number>(() => Math.max(0, props.expiresAt - props.now));
57-
const timeTotal = computed<number>(() => Math.max(0, props.expiresAt - props.createdAt));
58-
const timeLeftPercentage = computed<number>(() => Math.min(100, (timeLeft.value / timeTotal.value) * 100));
57+
const maxTimeLeft = computed<number>(() => Math.max(0, props.maxExpiresAt - props.now));
58+
const timeLeftPercentage = computed<number>(() => {
59+
if (maxTimeLeft.value === 0) return 0;
60+
return Math.min(100, (timeLeft.value / maxTimeLeft.value) * 100);
61+
});
5962
const isRevoked = computed(() => props.status === SessionStatus.Closed);
6063
</script>

0 commit comments

Comments
 (0)