Skip to content

Commit 10a3b91

Browse files
committed
fix: flaky template e2e tests, lint errors, and staticcheck warnings
Replace fixed waitForTimeout delays with proper element-based waits in template sending tests. Add waitForTemplatesLoaded() helper that waits for the spinner to disappear and template items to render. Fix all golangci-lint errcheck violations by handling Close/Remove return values. Apply De Morgan's law for staticcheck QF1001 and use tagged switch for QF1003.
1 parent 1b54493 commit 10a3b91

File tree

13 files changed

+91
-81
lines changed

13 files changed

+91
-81
lines changed

frontend/e2e/pages/ChatPage.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,17 +297,29 @@ export class ChatPage extends BasePage {
297297
await this.templateSearchInput.waitFor({ state: 'visible' })
298298
}
299299

300+
/** Wait for the loading spinner to disappear and at least one template item to appear */
301+
async waitForTemplatesLoaded() {
302+
// Wait for the loader to disappear (if it was shown)
303+
const loader = this.templatePopover.locator('.animate-spin')
304+
await loader.waitFor({ state: 'hidden', timeout: 10000 }).catch(() => {})
305+
// Wait for at least one template button to be visible
306+
await this.page.locator('button.w-full.text-left').first().waitFor({ state: 'visible', timeout: 10000 })
307+
}
308+
300309
async searchTemplates(term: string) {
301310
await this.templateSearchInput.fill(term)
302-
await this.page.waitForTimeout(300)
311+
// Wait for filtered results to settle
312+
await this.page.locator('button.w-full.text-left').first().waitFor({ state: 'visible', timeout: 10000 })
303313
}
304314

305315
getTemplateItem(name: string): Locator {
306316
return this.page.locator('button.w-full.text-left').filter({ hasText: name })
307317
}
308318

309319
async selectTemplate(name: string) {
310-
await this.getTemplateItem(name).click()
320+
const item = this.getTemplateItem(name)
321+
await item.waitFor({ state: 'visible', timeout: 10000 })
322+
await item.click()
311323
await this.templateDialog.waitFor({ state: 'visible' })
312324
}
313325

frontend/e2e/tests/chat/template-sending.spec.ts

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,7 @@ test.describe('Template Sending', () => {
129129
await chatPage.goto(contactId)
130130

131131
await chatPage.openTemplatePicker()
132-
133-
// Wait for templates to load (loader disappears)
134-
await page.waitForTimeout(1000)
132+
await chatPage.waitForTemplatesLoaded()
135133

136134
// At least our seeded templates should appear
137135
const templateItems = chatPage.templatePopover.locator('button.w-full.text-left')
@@ -145,11 +143,10 @@ test.describe('Template Sending', () => {
145143
await chatPage.goto(contactId)
146144

147145
await chatPage.openTemplatePicker()
148-
await page.waitForTimeout(500)
146+
await chatPage.waitForTemplatesLoaded()
149147

150148
// Search for our simple template
151149
await chatPage.searchTemplates('e2e_simple')
152-
await page.waitForTimeout(300)
153150

154151
// Should find the matching template
155152
const item = chatPage.getTemplateItem(`E2E Simple`)
@@ -162,11 +159,10 @@ test.describe('Template Sending', () => {
162159
await chatPage.goto(contactId)
163160

164161
await chatPage.openTemplatePicker()
165-
await page.waitForTimeout(500)
162+
await chatPage.waitForTemplatesLoaded()
166163

167164
// Search and select the simple template
168165
await chatPage.searchTemplates('e2e_simple')
169-
await page.waitForTimeout(300)
170166
await chatPage.selectTemplate(`E2E Simple`)
171167

172168
// Dialog should show "Preview" heading (no params to fill)
@@ -192,10 +188,9 @@ test.describe('Template Sending', () => {
192188
await chatPage.goto(contactId)
193189

194190
await chatPage.openTemplatePicker()
195-
await page.waitForTimeout(500)
191+
await chatPage.waitForTemplatesLoaded()
196192

197193
await chatPage.searchTemplates('e2e_params')
198-
await page.waitForTimeout(300)
199194
await chatPage.selectTemplate(`E2E Params`)
200195

201196
// Dialog should show "Fill Parameters" heading
@@ -218,10 +213,9 @@ test.describe('Template Sending', () => {
218213
await chatPage.goto(contactId)
219214

220215
await chatPage.openTemplatePicker()
221-
await page.waitForTimeout(500)
216+
await chatPage.waitForTemplatesLoaded()
222217

223218
await chatPage.searchTemplates('e2e_params')
224-
await page.waitForTimeout(300)
225219
await chatPage.selectTemplate(`E2E Params`)
226220

227221
// Fill parameters
@@ -242,10 +236,9 @@ test.describe('Template Sending', () => {
242236
await chatPage.goto(contactId)
243237

244238
await chatPage.openTemplatePicker()
245-
await page.waitForTimeout(500)
239+
await chatPage.waitForTemplatesLoaded()
246240

247241
await chatPage.searchTemplates('e2e_buttons')
248-
await page.waitForTimeout(300)
249242
await chatPage.selectTemplate(`E2E Buttons`)
250243

251244
// Preview should show the body
@@ -267,10 +260,9 @@ test.describe('Template Sending', () => {
267260
await chatPage.goto(contactId)
268261

269262
await chatPage.openTemplatePicker()
270-
await page.waitForTimeout(500)
263+
await chatPage.waitForTemplatesLoaded()
271264

272265
await chatPage.searchTemplates('e2e_simple')
273-
await page.waitForTimeout(300)
274266
await chatPage.selectTemplate(`E2E Simple`)
275267

276268
// Click send
@@ -297,10 +289,9 @@ test.describe('Template Sending', () => {
297289
await chatPage.goto(contactId)
298290

299291
await chatPage.openTemplatePicker()
300-
await page.waitForTimeout(500)
292+
await chatPage.waitForTemplatesLoaded()
301293

302294
await chatPage.searchTemplates('e2e_params')
303-
await page.waitForTimeout(300)
304295
await chatPage.selectTemplate(`E2E Params`)
305296

306297
// Fill only one parameter
@@ -326,10 +317,9 @@ test.describe('Template Sending', () => {
326317
await chatPage.goto(contactId)
327318

328319
await chatPage.openTemplatePicker()
329-
await page.waitForTimeout(500)
320+
await chatPage.waitForTemplatesLoaded()
330321

331322
await chatPage.searchTemplates('e2e_params')
332-
await page.waitForTimeout(300)
333323
await chatPage.selectTemplate(`E2E Params`)
334324

335325
// Fill all parameters
@@ -351,10 +341,9 @@ test.describe('Template Sending', () => {
351341
await chatPage.goto(contactId)
352342

353343
await chatPage.openTemplatePicker()
354-
await page.waitForTimeout(500)
344+
await chatPage.waitForTemplatesLoaded()
355345

356346
await chatPage.searchTemplates('e2e_simple')
357-
await page.waitForTimeout(300)
358347
await chatPage.selectTemplate(`E2E Simple`)
359348

360349
// Dialog should be open

internal/calling/audio.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (p *AudioPlayer) PlayFile(filePath string) (int, error) {
3737
if err != nil {
3838
return 0, fmt.Errorf("failed to open audio file: %w", err)
3939
}
40-
defer file.Close()
40+
defer file.Close() //nolint:errcheck
4141

4242
packets, err := readOpusPackets(file)
4343
if err != nil {

internal/calling/dtmf.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (m *Manager) handleDTMFTrack(session *CallSession, track *webrtc.TrackRemot
5252
endBit := (buf[1] & 0x80) != 0
5353

5454
// Debounce: only emit on the first end-bit packet for each event
55-
if endBit && !(lastEvent == eventID && lastEndBit) {
55+
if endBit && (lastEvent != eventID || !lastEndBit) {
5656
if digit, ok := dtmfDigits[eventID]; ok {
5757
m.log.Info("DTMF digit detected",
5858
"call_id", session.ID,

internal/calling/outgoing.go

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ func (m *Manager) InitiateOutgoingCall(
5353
"server-to-agent",
5454
)
5555
if err != nil {
56-
agentPC.Close()
56+
_ = agentPC.Close()
5757
return uuid.Nil, "", fmt.Errorf("failed to create agent local track: %w", err)
5858
}
5959
if _, err := agentPC.AddTrack(agentLocalTrack); err != nil {
60-
agentPC.Close()
60+
_ = agentPC.Close()
6161
return uuid.Nil, "", fmt.Errorf("failed to add agent local track: %w", err)
6262
}
6363

@@ -111,18 +111,18 @@ func (m *Manager) InitiateOutgoingCall(
111111
Type: webrtc.SDPTypeOffer,
112112
SDP: agentSDPOffer,
113113
}); err != nil {
114-
agentPC.Close()
114+
_ = agentPC.Close()
115115
return uuid.Nil, "", fmt.Errorf("failed to set agent remote desc: %w", err)
116116
}
117117

118118
// 6. Create SDP answer for agent
119119
agentAnswer, err := agentPC.CreateAnswer(nil)
120120
if err != nil {
121-
agentPC.Close()
121+
_ = agentPC.Close()
122122
return uuid.Nil, "", fmt.Errorf("failed to create agent answer: %w", err)
123123
}
124124
if err := agentPC.SetLocalDescription(agentAnswer); err != nil {
125-
agentPC.Close()
125+
_ = agentPC.Close()
126126
return uuid.Nil, "", fmt.Errorf("failed to set agent local desc: %w", err)
127127
}
128128

@@ -133,20 +133,20 @@ func (m *Manager) InitiateOutgoingCall(
133133
select {
134134
case <-gatherComplete:
135135
case <-ctx.Done():
136-
agentPC.Close()
136+
_ = agentPC.Close()
137137
return uuid.Nil, "", fmt.Errorf("agent ICE gathering timed out")
138138
}
139139

140140
agentSDP := agentPC.LocalDescription()
141141
if agentSDP == nil {
142-
agentPC.Close()
142+
_ = agentPC.Close()
143143
return uuid.Nil, "", fmt.Errorf("no agent local description")
144144
}
145145

146146
// 7. Create WhatsApp PeerConnection
147147
waPC, err := m.createPeerConnection()
148148
if err != nil {
149-
agentPC.Close()
149+
_ = agentPC.Close()
150150
return uuid.Nil, "", fmt.Errorf("failed to create WA PC: %w", err)
151151
}
152152

@@ -157,13 +157,13 @@ func (m *Manager) InitiateOutgoingCall(
157157
"server-to-wa",
158158
)
159159
if err != nil {
160-
agentPC.Close()
161-
waPC.Close()
160+
_ = agentPC.Close()
161+
_ = waPC.Close()
162162
return uuid.Nil, "", fmt.Errorf("failed to create WA local track: %w", err)
163163
}
164164
if _, err := waPC.AddTrack(waLocalTrack); err != nil {
165-
agentPC.Close()
166-
waPC.Close()
165+
_ = agentPC.Close()
166+
_ = waPC.Close()
167167
return uuid.Nil, "", fmt.Errorf("failed to add WA local track: %w", err)
168168
}
169169

@@ -202,13 +202,13 @@ func (m *Manager) InitiateOutgoingCall(
202202
// 10. Create SDP offer for WhatsApp
203203
waOffer, err := waPC.CreateOffer(nil)
204204
if err != nil {
205-
agentPC.Close()
206-
waPC.Close()
205+
_ = agentPC.Close()
206+
_ = waPC.Close()
207207
return uuid.Nil, "", fmt.Errorf("failed to create WA offer: %w", err)
208208
}
209209
if err := waPC.SetLocalDescription(waOffer); err != nil {
210-
agentPC.Close()
211-
waPC.Close()
210+
_ = agentPC.Close()
211+
_ = waPC.Close()
212212
return uuid.Nil, "", fmt.Errorf("failed to set WA local desc: %w", err)
213213
}
214214

@@ -218,15 +218,15 @@ func (m *Manager) InitiateOutgoingCall(
218218
select {
219219
case <-waGatherComplete:
220220
case <-ctx2.Done():
221-
agentPC.Close()
222-
waPC.Close()
221+
_ = agentPC.Close()
222+
_ = waPC.Close()
223223
return uuid.Nil, "", fmt.Errorf("WA ICE gathering timed out")
224224
}
225225

226226
waLocalDesc := waPC.LocalDescription()
227227
if waLocalDesc == nil {
228-
agentPC.Close()
229-
waPC.Close()
228+
_ = agentPC.Close()
229+
_ = waPC.Close()
230230
return uuid.Nil, "", fmt.Errorf("no WA local description")
231231
}
232232

@@ -236,8 +236,8 @@ func (m *Manager) InitiateOutgoingCall(
236236

237237
callID, err := m.whatsapp.InitiateCall(callCtx, waAccount, contactPhone, waLocalDesc.SDP)
238238
if err != nil {
239-
agentPC.Close()
240-
waPC.Close()
239+
_ = agentPC.Close()
240+
_ = waPC.Close()
241241
// Update call log as failed
242242
m.db.Model(&callLog).Updates(map[string]any{
243243
"status": models.CallStatusFailed,

internal/calling/recorder.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ func NewCallRecorder() (*CallRecorder, error) {
6363

6464
// Write OpusHead and OpusTags header pages
6565
if err := r.writeHeaders(); err != nil {
66-
f.Close()
67-
os.Remove(f.Name())
66+
_ = f.Close()
67+
_ = os.Remove(f.Name())
6868
return nil, err
6969
}
7070

@@ -107,7 +107,7 @@ func (r *CallRecorder) Stop() (string, int) {
107107
r.flushPage(true)
108108
}
109109

110-
r.file.Close()
110+
_ = r.file.Close()
111111
return r.path, r.packetCount
112112
}
113113

@@ -201,7 +201,9 @@ func (r *CallRecorder) flushPage(lastPage bool) {
201201
checksum := oggCRC32(page)
202202
binary.LittleEndian.PutUint32(page[22:26], checksum)
203203

204-
r.file.Write(page)
204+
if _, err := r.file.Write(page); err != nil {
205+
return
206+
}
205207
r.pageSeqNo++
206208

207209
// Clear buffer

internal/calling/session.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ func (m *Manager) newRecorderIfEnabled() *CallRecorder {
312312
// finalizeRecording stops the recorder, uploads the OGG file to S3, and updates the CallLog.
313313
func (m *Manager) finalizeRecording(orgID, callLogID uuid.UUID, recorder *CallRecorder) {
314314
path, packetCount := recorder.Stop()
315-
defer os.Remove(path)
315+
defer func() { _ = os.Remove(path) }()
316316

317317
if packetCount == 0 {
318318
return
@@ -329,7 +329,7 @@ func (m *Manager) finalizeRecording(orgID, callLogID uuid.UUID, recorder *CallRe
329329
m.log.Error("Failed to open recording file", "error", err, "call_log_id", callLogID)
330330
return
331331
}
332-
defer f.Close()
332+
defer f.Close() //nolint:errcheck
333333

334334
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
335335
defer cancel()

0 commit comments

Comments
 (0)