Skip to content

Commit 959ea0b

Browse files
authored
Merge branch 'main' into feature/unicorn-integration
2 parents 85b6336 + 31c295d commit 959ea0b

File tree

4 files changed

+693
-7
lines changed

4 files changed

+693
-7
lines changed

e2e/specs/stateful/settings-primary-name.spec.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ test.describe('Settings Tab Primary Names', () => {
3232
await expect(page.getByText(ensNames[1]).first()).toBeVisible({ timeout: 20000 })
3333
await expect(page.getByText(ensNames[2]).first()).toBeVisible({ timeout: 20000 })
3434
await expect(page.getByText(ensNames[3]).first()).toBeVisible({ timeout: 20000 })
35-
await expect(page.getByText(ensNames[4]).first()).toBeVisible({ timeout: 20000 })
35+
// Linea having issues, re-add once fixed
36+
// await expect(page.getByText(ensNames[4]).first()).toBeVisible({ timeout: 20000 })
3637

3738
await expect(page.getByTestId(`network-row-${ensNames[0]}`)).toBeVisible()
3839
await expect(page.getByTestId(`network-row-${ensNames[1]}`)).toBeVisible()
3940
await expect(page.getByTestId(`network-row-${ensNames[2]}`)).toBeVisible()
4041
await expect(page.getByTestId(`network-row-${ensNames[3]}`)).toBeVisible()
41-
await expect(page.getByTestId(`network-row-${ensNames[4]}`)).toBeVisible()
42+
// await expect(page.getByTestId(`network-row-${ensNames[4]}`)).toBeVisible()
4243

4344
// Check network icons are showing correctly (some networks may not have data in test environment)
4445
await expect(page.getByTestId(`network-icon-${ensNames[0]}-eth`)).toBeVisible()
@@ -56,6 +57,6 @@ test.describe('Settings Tab Primary Names', () => {
5657
await checkOptionalNetworkIcon(ensNames[1], 'arb1')
5758
await checkOptionalNetworkIcon(ensNames[2], 'base')
5859
await checkOptionalNetworkIcon(ensNames[3], 'op')
59-
await checkOptionalNetworkIcon(ensNames[4], 'linea')
60+
// await checkOptionalNetworkIcon(ensNames[4], 'linea')
6061
})
6162
})
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
import { BrowserContext, expect, Page, test } from '@playwright/test'
2+
import dappwright from '@tenkeylabs/dappwright'
3+
import type { Dappwright } from '@tenkeylabs/dappwright'
4+
5+
import { dateToDateInput, roundDurationWithDay, secondsToDateInput } from '@app/utils/date'
6+
7+
import { SafeEnsConfig } from './config/safe-ens-config'
8+
9+
// Global variables to share state
10+
let metaMask: Dappwright
11+
let page: Page
12+
let context: BrowserContext
13+
14+
// Connect wallet to ENS app Sepolia
15+
async function connectWalletToEns(): Promise<void> {
16+
console.log('🔗 Connecting MetaMask to Sepolia ENS...')
17+
await page.goto('https://sepolia.app.ens.domains')
18+
await page.waitForTimeout(3000)
19+
20+
// Wait for "Connect Wallet" button and click
21+
const connectButton = page
22+
.locator(
23+
'button:has-text("Connect"), button:has-text("Connect Wallet"), [data-testid="connect-button"]',
24+
)
25+
.first()
26+
await connectButton.waitFor({ timeout: 15000 })
27+
await connectButton.click()
28+
console.log('🔘 Connect Wallet button clicked')
29+
await page.waitForTimeout(1000)
30+
31+
// Wait for wallet modal
32+
const modal = page.locator('[role="dialog"], .wallet-modal')
33+
await modal.waitFor({ timeout: 15000 })
34+
console.log('💬 Wallet modal detected')
35+
36+
// Wait for MetaMask option inside modal
37+
const metamaskOption = modal.locator('button', { hasText: 'MetaMask' }).first()
38+
await metamaskOption.waitFor({ timeout: 15000 })
39+
await metamaskOption.click()
40+
console.log('🦊 MetaMask option clicked, waiting for extension popup...')
41+
42+
// Poll for MetaMask notification popup
43+
let mmPage
44+
let attempts = 0
45+
46+
while (attempts < 20 && !mmPage) {
47+
mmPage = context
48+
.pages()
49+
.find((p) => p.url().includes('chrome-extension://') && p.url().includes('notification.html'))
50+
51+
if (mmPage) break
52+
// eslint-disable-next-line no-await-in-loop
53+
await page.waitForTimeout(500)
54+
55+
attempts += 1
56+
}
57+
58+
if (!mmPage) {
59+
throw new Error('MetaMask popup not found')
60+
}
61+
62+
await mmPage.bringToFront()
63+
64+
// Optional: select first account if visible
65+
const accountButton = mmPage.locator('div.account-list button').first()
66+
if (await accountButton.isVisible({ timeout: 5000 })) {
67+
await accountButton.click()
68+
const nextButton = mmPage.locator('button:has-text("Next")').first()
69+
if (await nextButton.isVisible({ timeout: 3000 })) {
70+
await nextButton.click()
71+
}
72+
}
73+
74+
// Confirm connection
75+
const confirmButton = mmPage
76+
.locator('button:has-text("Connect"), button:has-text("Confirm"), .btn-primary')
77+
.first()
78+
await confirmButton.waitFor({ timeout: 5000 })
79+
await confirmButton.click()
80+
console.log('✅ MetaMask connection confirmed')
81+
82+
// Bring main page to front and wait a few seconds
83+
await page.bringToFront()
84+
await page.waitForTimeout(3000)
85+
86+
// Optional: verify connection
87+
const stillVisible = await page
88+
.locator('button:has-text("Connect"), [data-testid="connect-button"]')
89+
.isVisible()
90+
if (stillVisible) {
91+
console.log('⚠️ Wallet may not have connected — check MetaMask popup manually')
92+
} else {
93+
console.log('✅ Wallet successfully connected on ENS site')
94+
}
95+
}
96+
97+
// Confirm transaction helper
98+
async function confirmTransactionWithMetaMask(): Promise<void> {
99+
console.log(`🦊 Waiting for MetaMask popup...`)
100+
101+
// Listen for a new popup page to open
102+
const [mmPage] = await Promise.all([
103+
page.context().waitForEvent('page', { timeout: 15000 }), // wait up to 15s
104+
// Ensure we click or trigger the action that opens the popup BEFORE this function is called
105+
])
106+
107+
// Verify this is actually a MetaMask notification page
108+
if (
109+
!mmPage.url().includes('chrome-extension://') ||
110+
!mmPage.url().includes('notification.html')
111+
) {
112+
throw new Error(`Unexpected popup detected: ${mmPage.url()}`)
113+
}
114+
115+
await mmPage.bringToFront()
116+
117+
// Wait for confirm button to appear and click it
118+
const confirmButton = mmPage.locator('button:has-text("Confirm")')
119+
await confirmButton.waitFor({ timeout: 10000 })
120+
await confirmButton.click()
121+
122+
console.log(`✅ MetaMask transaction confirmed`)
123+
124+
await page.bringToFront()
125+
}
126+
127+
// Extend owned name
128+
async function extendOwnedNameOnSepoliaApp(): Promise<void> {
129+
const name = 'extend-name-test.eth'
130+
131+
console.log(`🎯 Starting extension for ${name}`)
132+
133+
// Search for name
134+
const searchInput = page.locator('input[placeholder="Search for a name"]')
135+
await searchInput.waitFor({ timeout: 15000 })
136+
await searchInput.fill(name)
137+
await searchInput.press('Enter')
138+
139+
// Grab the current expiry timestamp from profile
140+
const expiryElement = page.getByTestId('owner-profile-button-name.expiry')
141+
await expiryElement.waitFor({ state: 'visible', timeout: 15000 })
142+
143+
const timestampAttr = await expiryElement.getAttribute('data-timestamp')
144+
if (!timestampAttr) throw new Error('❌ Could not read expiry timestamp from UI')
145+
146+
const currentExpiryTimestamp = parseInt(timestampAttr, 10)
147+
console.log(`📅 Current expiry: ${new Date(currentExpiryTimestamp).toISOString()}`)
148+
149+
// Click "extend"
150+
const extendButton = page.getByTestId('extend-button')
151+
await extendButton.waitFor({ state: 'visible', timeout: 15000 })
152+
await extendButton.click()
153+
154+
// Switch to "pick by date"
155+
const dateSelection = page.getByTestId('date-selection')
156+
await expect(dateSelection).toHaveText('Pick by date')
157+
await dateSelection.click()
158+
159+
// Fill the calendar with a date one day later
160+
const expiryTime = currentExpiryTimestamp / 1000
161+
const calendar = page.getByTestId('calendar')
162+
const dayLater = await page.evaluate((ts) => {
163+
const expiryDate = new Date(ts)
164+
expiryDate.setDate(expiryDate.getDate() + 1)
165+
return expiryDate
166+
}, currentExpiryTimestamp)
167+
168+
await calendar.fill(dateToDateInput(dayLater))
169+
await expect(page.getByTestId('calendar-date')).toHaveValue(
170+
secondsToDateInput(expiryTime + roundDurationWithDay(dayLater, expiryTime)),
171+
)
172+
173+
// Confirm extension
174+
const confirmButton = page.getByTestId('extend-names-confirm')
175+
await confirmButton.click()
176+
177+
// Transaction modal check BEFORE confirming
178+
await expect(page.getByText('1 day')).toBeVisible()
179+
await page.locator('text=Open Wallet').waitFor({ timeout: 10000 })
180+
await page.locator('text=Open Wallet').click()
181+
182+
// Confirm in Metamask pop up
183+
await confirmTransactionWithMetaMask()
184+
185+
// Wait for transaction to complete
186+
await page.waitForTimeout(25000)
187+
188+
// Close transaction complete modal
189+
const transactionCompleteButton = page.getByTestId('transaction-modal-complete-button')
190+
await transactionCompleteButton.click()
191+
192+
// Verify new expiry is +1 day
193+
const newTimestampAttr = await expiryElement.getAttribute('data-timestamp')
194+
if (!newTimestampAttr) throw new Error('❌ Could not read new expiry timestamp')
195+
196+
const newExpiryTimestamp = parseInt(newTimestampAttr, 10)
197+
const expectedDate = new Date(currentExpiryTimestamp)
198+
expectedDate.setDate(expectedDate.getDate() + 1)
199+
const expectedTimestamp = expectedDate.getTime()
200+
201+
expect(newExpiryTimestamp).toBe(expectedTimestamp)
202+
console.log(
203+
`✅ Name successfully extended by 1 day (new expiry: ${new Date(
204+
newExpiryTimestamp,
205+
).toISOString()})`,
206+
)
207+
}
208+
209+
// Extend unowned name
210+
async function extendUnownedNameSepolia(): Promise<void> {
211+
const name = 'user1-extend.eth'
212+
213+
console.log(`🎯 Starting extension for ${name}`)
214+
215+
// Search for name
216+
const searchInput = page.locator('input[placeholder="Search for a name"]')
217+
await searchInput.waitFor({ timeout: 15000 })
218+
await searchInput.fill(name)
219+
await searchInput.press('Enter')
220+
221+
// Grab the current expiry timestamp from profile
222+
const expiryElement = page.getByTestId('owner-profile-button-name.expiry')
223+
await expiryElement.waitFor({ state: 'visible', timeout: 15000 })
224+
225+
const timestampAttr = await expiryElement.getAttribute('data-timestamp')
226+
if (!timestampAttr) throw new Error('❌ Could not read expiry timestamp from UI')
227+
228+
const currentExpiryTimestamp = parseInt(timestampAttr, 10)
229+
console.log(`📅 Current expiry: ${new Date(currentExpiryTimestamp).toISOString()}`)
230+
231+
// Click "extend"
232+
const extendButton = page.getByTestId('extend-button')
233+
await extendButton.waitFor({ state: 'visible', timeout: 15000 })
234+
await extendButton.click()
235+
236+
// Acknowledge extension of unowned name
237+
const extendUnownedConfirm = page.getByTestId('extend-names-confirm')
238+
await extendUnownedConfirm.waitFor({ state: 'visible', timeout: 5000 })
239+
await extendUnownedConfirm.click()
240+
241+
// Switch to "pick by date"
242+
const dateSelection = page.getByTestId('date-selection')
243+
await expect(dateSelection).toHaveText('Pick by date', { timeout: 5000 })
244+
await dateSelection.click()
245+
246+
// Fill the calendar with a date one day later
247+
const expiryTime = currentExpiryTimestamp / 1000
248+
const calendar = page.getByTestId('calendar')
249+
const dayLater = await page.evaluate((ts) => {
250+
const expiryDate = new Date(ts)
251+
expiryDate.setDate(expiryDate.getDate() + 1)
252+
return expiryDate
253+
}, currentExpiryTimestamp)
254+
255+
await calendar.fill(dateToDateInput(dayLater))
256+
await expect(page.getByTestId('calendar-date')).toHaveValue(
257+
secondsToDateInput(expiryTime + roundDurationWithDay(dayLater, expiryTime)),
258+
)
259+
260+
// Confirm extension
261+
const confirmButton = page.getByTestId('extend-names-confirm')
262+
await confirmButton.click()
263+
264+
// Transaction modal check BEFORE confirming
265+
await expect(page.getByText('1 day')).toBeVisible()
266+
await page.locator('text=Open Wallet').waitFor({ timeout: 10000 })
267+
await page.locator('text=Open Wallet').click()
268+
269+
// Confirm in Metamask pop up
270+
await confirmTransactionWithMetaMask()
271+
272+
// Wait for transaction to complete
273+
await page.waitForTimeout(15000)
274+
275+
// Close transaction complete modal
276+
const transactionCompleteButton = page.getByTestId('transaction-modal-complete-button')
277+
await transactionCompleteButton.click()
278+
279+
// Verify new expiry is +1 day
280+
const newTimestampAttr = await expiryElement.getAttribute('data-timestamp')
281+
if (!newTimestampAttr) throw new Error('❌ Could not read new expiry timestamp')
282+
283+
const newExpiryTimestamp = parseInt(newTimestampAttr, 10)
284+
const expectedDate = new Date(currentExpiryTimestamp)
285+
expectedDate.setDate(expectedDate.getDate() + 1)
286+
const expectedTimestamp = expectedDate.getTime()
287+
288+
expect(newExpiryTimestamp).toBe(expectedTimestamp)
289+
console.log(
290+
`✅ Name successfully extended by 1 day (new expiry: ${new Date(
291+
newExpiryTimestamp,
292+
).toISOString()})`,
293+
)
294+
}
295+
296+
test.describe('ENS Sepolia Extend Name', () => {
297+
test.beforeAll('Setup Metamask', async () => {
298+
console.log('🦊 Setting up MetaMask...')
299+
const [mm, pg, ctx] = await dappwright.bootstrap('chromium', {
300+
wallet: 'metamask',
301+
version: SafeEnsConfig.METAMASK.VERSION,
302+
seed: SafeEnsConfig.SEED_PHRASE,
303+
password: SafeEnsConfig.WALLET_PASSWORD,
304+
headless: SafeEnsConfig.BROWSER.HEADLESS,
305+
slowMo: SafeEnsConfig.BROWSER.SLOW_MO,
306+
})
307+
308+
metaMask = mm
309+
page = pg
310+
context = ctx
311+
312+
console.log('✅ MetaMask setup complete')
313+
314+
// Switch to User 2 account
315+
await page.click('[data-testid="account-menu-icon"]')
316+
await page.click('[data-testid="multichain-account-menu-popover-action-button"]')
317+
await page.click('[data-testid="multichain-account-menu-popover-add-account"]')
318+
await page.click('[data-testid="submit-add-account-with-name"]')
319+
320+
console.log('✅ Switched to User 2 account')
321+
322+
try {
323+
await metaMask.switchNetwork('Sepolia')
324+
console.log('✅ Switched to Sepolia network')
325+
} catch (error) {
326+
console.log('⚠️ Could not switch to Sepolia:', error)
327+
}
328+
329+
// Connect wallet to ENS Sepolia
330+
await connectWalletToEns()
331+
})
332+
333+
test('Connect MetaMask to ENS Sepolia', async () => {
334+
await expect(
335+
page.locator('button:has-text("Connect"), [data-testid="connect-button"]'),
336+
).toBeHidden({ timeout: 5000 })
337+
338+
console.log('✅ Wallet is connected and ready')
339+
})
340+
341+
test('Extend user owned ENS name on Sepolia', async () => {
342+
await extendOwnedNameOnSepoliaApp()
343+
})
344+
345+
test('Extend not user owned ENS name on Sepolia', async () => {
346+
await extendUnownedNameSepolia()
347+
})
348+
})

0 commit comments

Comments
 (0)