Просмотр исходного кода

chore: fix flakey test in suggestionsBrowserKey

jamesread 2 недель назад
Родитель
Сommit
1499d5bdf5

+ 25 - 1
frontend/js/websocket.js

@@ -8,12 +8,20 @@ const BANNER_DELAY_MS = 2000
 let reconnectAttempt = 0
 let reconnectTimer = null
 let listenersInitialized = false
+let eventStreamGeneration = 0
+let eventStreamAbortController = null
 
 function shouldConnectEventStream () {
   return window.initResponse && !window.initResponse.loginRequired
 }
 
 export function stopEventStream () {
+  eventStreamGeneration++
+  if (eventStreamAbortController != null) {
+    eventStreamAbortController.abort()
+    eventStreamAbortController = null
+  }
+
   if (reconnectTimer != null) {
     clearTimeout(reconnectTimer)
     reconnectTimer = null
@@ -105,6 +113,12 @@ async function reconnectWebsocket () {
     return
   }
 
+  const streamGeneration = ++eventStreamGeneration
+  if (eventStreamAbortController != null) {
+    eventStreamAbortController.abort()
+  }
+  eventStreamAbortController = new AbortController()
+
   connectionState.reconnecting = true
   connectionState.connected = false
   if (connectionState.disconnectedAt == null) {
@@ -115,7 +129,7 @@ async function reconnectWebsocket () {
 
   try {
     window.websocketAvailable = true
-    const stream = window.client.eventStream()
+    const stream = window.client.eventStream({}, { signal: eventStreamAbortController.signal })
     connectionState.connected = true
     connectionState.reconnecting = false
     connectionState.disconnectedAt = null
@@ -123,15 +137,25 @@ async function reconnectWebsocket () {
     connectionState.scheduledReconnectDelayMs = 0
     connectionState.showDisconnectedBanner = false
     for await (const e of stream) {
+      if (streamGeneration !== eventStreamGeneration) {
+        return
+      }
       if (reconnectAttempt !== 0) {
         reconnectAttempt = 0
       }
       handleEvent(e)
     }
   } catch (err) {
+    if (streamGeneration !== eventStreamGeneration) {
+      return
+    }
     console.error('Websocket connection failed: ', err)
   }
 
+  if (streamGeneration !== eventStreamGeneration) {
+    return
+  }
+
   window.websocketAvailable = false
   connectionState.connected = false
   connectionState.reconnecting = false

+ 44 - 25
frontend/resources/vue/views/ArgumentForm.vue

@@ -45,7 +45,7 @@
         </div>
 
         <div class="buttons">
-          <button name="start" type="submit" :disabled="hasConfirmation && !confirmationChecked">
+          <button name="start" type="submit" :disabled="!formReady || (hasConfirmation && !confirmationChecked)">
             Start
           </button>
           <button name="cancel" type="button" @click="handleCancel">
@@ -58,7 +58,7 @@
 </template>
 
 <script setup>
-import { ref, onMounted, nextTick } from 'vue'
+import { ref, onMounted, onUnmounted, nextTick } from 'vue'
 import { useRouter } from 'vue-router'
 import { requestReconnectNow } from '../../../js/websocket.js'
 
@@ -75,6 +75,7 @@ const hasConfirmation = ref(false)
 const formErrors = ref({})
 const actionArguments = ref([])
 const popupOnStart = ref('')
+const formReady = ref(false)
 
 // Computed properties
 
@@ -87,23 +88,27 @@ const props = defineProps({
 
 // Methods
 async function setup() {
-  const ret = await window.client.getActionBinding({
-    bindingId: props.bindingId
-  })
-
-  const action = ret.action
-
-  title.value = action.title
-  icon.value = action.icon
-  popupOnStart.value = action.popupOnStart || ''
-  actionArguments.value = action.arguments || []
-  argValues.value = {}
-  formErrors.value = {}
-  confirmationChecked.value = false
-  hasConfirmation.value = false
-
-  // Initialize values from query params or defaults
-  actionArguments.value.forEach(arg => {
+  formReady.value = false
+  document.body.removeAttribute('loaded-argument-form')
+
+  try {
+    const ret = await window.client.getActionBinding({
+      bindingId: props.bindingId
+    })
+
+    const action = ret.action
+
+    title.value = action.title
+    icon.value = action.icon
+    popupOnStart.value = action.popupOnStart || ''
+    actionArguments.value = action.arguments || []
+    argValues.value = {}
+    formErrors.value = {}
+    confirmationChecked.value = false
+    hasConfirmation.value = false
+
+    // Initialize values from query params or defaults
+    actionArguments.value.forEach(arg => {
     if (arg.type === 'confirmation') {
       hasConfirmation.value = true
       const paramValue = getQueryParamValue(arg.name)
@@ -130,14 +135,20 @@ async function setup() {
         argValues.value[arg.name] = paramValue !== null ? paramValue : arg.defaultValue || ''
       }
     }
-  })
+    })
 
-  // Run initial validation on all fields after DOM is updated
-  await nextTick()
-  for (const arg of actionArguments.value) {
-    if (arg.type && !arg.type.startsWith('regex:') && arg.type !== 'select' && arg.type !== '' && arg.type !== 'confirmation' && arg.type !== 'checkbox') {
-      await validateArgument(arg, argValues.value[arg.name] || '')
+    // Run initial validation on all fields after DOM is updated
+    await nextTick()
+    for (const arg of actionArguments.value) {
+      if (arg.type && !arg.type.startsWith('regex:') && arg.type !== 'select' && arg.type !== '' && arg.type !== 'confirmation' && arg.type !== 'checkbox') {
+        await validateArgument(arg, argValues.value[arg.name] || '')
+      }
     }
+
+    formReady.value = true
+    document.body.setAttribute('loaded-argument-form', props.bindingId)
+  } catch (err) {
+    console.error('Failed to load argument form:', err)
   }
 }
 
@@ -394,6 +405,10 @@ async function startAction(actionArgs) {
 async function handleSubmit(event) {
   event.preventDefault()
 
+  if (!formReady.value) {
+    return
+  }
+
   if (popupOnStart.value === 'history') {
     router.push(`/action/${props.bindingId}`)
     return
@@ -470,6 +485,10 @@ defineExpose({
 onMounted(() => {
   setup()
 })
+
+onUnmounted(() => {
+  document.body.removeAttribute('loaded-argument-form')
+})
 </script>
 
 <style scoped>

+ 10 - 1
integration-tests/tests/suggestionsBrowserKey/suggestionsBrowserKey.mjs

@@ -19,6 +19,15 @@ async function openArgumentForm() {
     }),
     5000
   )
+
+  await webdriver.wait(
+    new Condition('wait for argument form ready', async () => {
+      const body = await webdriver.findElement(By.css('body'))
+      const attr = await body.getAttribute('loaded-argument-form')
+      return attr != null && attr !== ''
+    }),
+    5000
+  )
 }
 
 async function getTestInput() {
@@ -62,7 +71,7 @@ async function waitForExecutionComplete() {
       try {
         const statusElement = await webdriver.findElement(By.id('execution-dialog-status'))
         const statusText = await statusElement.getText()
-        return !statusText.includes('Executing')
+        return !statusText.includes('Still running')
       } catch (e) {
         return false
       }