Nuestro conocimiento compartido. Nuestro tesoro compartido. Wikipedia.
ShareCode
Permalink: http://www.treeweb.es/u/974/ 01/02/2011

ShareCode

1 <!DOCTYPE html>2 <html lang="es">3 <head>4  <meta charset="UTF-8">5  <title>Plataforma de Testing Automatizado de APIs</title>6  <!-- Enlaces a Bootstrap CSS y Bootstrap Icons -->7  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">8  <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">9  <!-- Estilos personalizados -->10  <style>11  body {12  background-color: var(--bs-body-bg);13  color: var(--bs-body-color);14  transition: background-color 0.3s, color 0.3s;15  }16  .step {17  display: flex;18  align-items: flex-start;19  padding: 8px 12px;20  margin-bottom: 8px;21  position: relative;22  }23  .step:hover {24  background-color: #f8f9fa;25  }26  .step:hover .step-controls {27  display: flex;28  }29  .step-type {30  font-weight: bold;31  color: #0d6efd;32  margin-right: 10px;33  min-width: 60px;34  }35  .step-content {36  flex: 1;37  display: flex;38  flex-direction: column;39  }40  .step-content .main-content {41  display: flex;42  align-items: center;43  flex-wrap: wrap;44  }45  .step-content input,46  .step-content select,47  .step-content textarea {48  margin-right: 8px;49  }50  .step-controls {51  display: none;52  align-items: center;53  margin-left: 5px;54  }55  .step-controls button {56  background: none;57  border: none;58  margin-left: 5px;59  color: #6c757d;60  }61  .step-controls button:hover {62  color: #000;63  }64  .add-step {65  text-align: center;66  margin: 20px 0;67  }68  .theme-toggle {69  cursor: pointer;70  }71  .execute-btn {72  background: none;73  border: none;74  color: #198754;75  font-size: 1.2em;76  }77  .execute-btn:hover {78  color: #145c32;79  }80  .result-panel {81  margin-top: 10px;82  }83  .deferred-label {84  margin-left: 10px;85  font-size: 0.9em;86  color: #6c757d;87  }88  /* Estilo para el borde izquierdo de los detalles */89  .step-content .details {90  border-left: 2px solid #0d6efd;91  padding-left: 10px;92  margin-left: 10px;93  }94  .result-toggle {95  background: none;96  border: none;97  color: #0d6efd;98  padding: 0;99  font-size: 0.9em;100  }101  /* Estilos para los botones de Guardar y Cargar */102  .flow-controls {103  text-align: center;104  margin-bottom: 20px;105  }106  .flow-controls .btn {107  margin-right: 10px;108  }109  /* Ocultar input file */110  #fileInput {111  display: none;112  }113  </style>114 </head>115 <body>116  <!-- Barra de navegación -->117  <nav class="navbar navbar-expand-lg navbar-dark bg-primary">118  <div class="container-fluid">119  <a class="navbar-brand" href="#">120  <!-- Logo -->121  <img src="https://via.placeholder.com/150x40?text=Logo" alt="Logo">122  </a>123  <span class="navbar-text text-white">124  Plataforma de Testing Automatizado de APIs125  </span>126  <button class="btn btn-secondary ms-auto theme-toggle" id="themeToggle">127  <i class="bi bi-moon-fill"></i>128  </button>129  </div>130  </nav>131 132  <!-- Contenido principal -->133  <div id="app" class="container my-4">134  <!-- Botones para Guardar y Cargar Flujos -->135  <div class="flow-controls">136  <button class="btn btn-outline-secondary" @click="saveFlow">137  <i class="bi bi-save"></i> Guardar Flujo138  </button>139  <button class="btn btn-outline-secondary" @click="loadFlow">140  <i class="bi bi-folder2-open"></i> Cargar Flujo141  </button>142  <!-- Input file oculto para cargar archivos -->143  <input type="file" id="fileInput" @change="handleFileUpload" accept=".json">144  </div>145 146  <!-- Lista de pasos -->147  <div v-for="(step, index) in steps" :key="index" class="step">148  <span class="step-type">{{ getStepTypeLabel(step.type, step.data.method) }}</span>149 150  <div class="step-content">151  <div class="main-content">152  <!-- Contenido según el tipo de paso -->153  <component :is="getStepComponent(step.type)" v-model="step.data" :step="step"></component>154  <!-- Checkbox para "Deferred" -->155  <div v-if="step.type === 'request' || step.type === 'assertion'">156  <input type="checkbox" v-model="step.deferred" :id="'deferred-' + index">157  <label :for="'deferred-' + index" class="deferred-label">Deferred</label>158  </div>159  </div>160  <!-- Resultados dentro del bloque de solicitud -->161  <div v-if="step.type === 'request' && step.result" class="result-panel">162  <button class="result-toggle" @click="step.showResult = !step.showResult">163  <i class="bi" :class="step.showResult ? 'bi-eye-slash' : 'bi-eye'"></i>164  {{ step.showResult ? 'Ocultar' : 'Mostrar' }} Resultado165  </button>166  <div v-if="step.showResult">167  <div class="alert mt-2" :class="{'alert-success': step.result.success, 'alert-danger': !step.result.success}">168  <strong>Resultado:</strong> {{ step.result.message }}169  </div>170  <div v-if="step.result.details">171  <p><strong>Status Code:</strong> {{ step.result.details.status }}</p>172  <p><strong>Headers:</strong></p>173  <pre>{{ step.result.details.headers }}</pre>174  <p><strong>Body:</strong></p>175  <pre style="max-height: 200px; overflow-y: auto;">{{ step.result.details.body }}</pre>176  </div>177  </div>178  </div>179  </div>180  <div class="step-controls">181  <button @click="moveUp(index)" :disabled="index === 0"><i class="bi bi-arrow-up"></i></button>182  <button @click="moveDown(index)" :disabled="index === steps.length - 1"><i class="bi bi-arrow-down"></i></button>183  <button v-if="step.type === 'request'" @click="executeRequestStep(index)" class="execute-btn"><i class="bi bi-play-circle"></i></button>184  <button @click="removeStep(index)"><i class="bi bi-trash"></i></button>185  </div>186  </div>187 188  <!-- Botones para añadir nuevos pasos -->189  <div class="add-step">190  <button class="btn btn-outline-primary me-2" @click="addStep('variable')">191  <i class="bi bi-sliders"></i> Variable192  </button>193  <button class="btn btn-outline-primary me-2" @click="addStep('request')">194  <i class="bi bi-arrow-up-right-circle"></i> Solicitud HTTP195  </button>196  <button class="btn btn-outline-primary me-2" @click="addStep('assertion')">197  <i class="bi bi-check-circle"></i> Aserción198  </button>199  <button class="btn btn-outline-primary me-2" @click="addStep('wait')">200  <i class="bi bi-hourglass-split"></i> Wait201  </button>202  <!-- Agrega más botones si es necesario -->203  </div>204 205  <!-- Botón de Ejecución -->206  <div class="text-center my-4">207  <button class="btn btn-lg btn-success" @click="executeTest" :disabled="!isExecutable">208  <i class="bi bi-play-fill"></i> Ejecutar Prueba Completa209  </button>210  </div>211  </div>212 213  <!-- Enlaces a Vue.js, Axios y Bootstrap JS -->214  <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>215  <script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>216  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>217 218  <!-- Componentes para los pasos -->219  <script>220  const VariableStep = {221  template: `222  <div class="d-flex align-items-center">223  <input type="text" class="form-control me-2" v-model="data.name" placeholder="Nombre" style="max-width: 150px;">224  <span>=</span>225  <input type="text" class="form-control ms-2" v-model="data.value" placeholder="Valor" style="max-width: 150px;">226  </div>227  `,228  props: ['modelValue', 'step'],229  computed: {230  data: {231  get() { return this.modelValue; },232  set(value) { this.$emit('update:modelValue', value); }233  }234  }235  };236 237  const RequestStep = {238  template: `239  <div>240  <div class="d-flex align-items-center flex-wrap">241  <select class="form-select me-2" v-model="data.method" style="max-width: 100px;">242  <option>GET</option>243  <option>POST</option>244  <option>PUT</option>245  <option>DELETE</option>246  </select>247  <input type="text" class="form-control me-2" v-model="data.url" placeholder="URL" style="flex: 1;">248  <!-- Botón para mostrar detalles -->249  <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleDetails">250  <i class="bi" :class="step.expanded ? 'bi-chevron-up' : 'bi-chevron-down'"></i>251  </button>252  </div>253  <!-- Detalles opcionales -->254  <div v-if="step.expanded" class="details mt-2">255  <!-- Headers -->256  <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleHeaders">257  <i class="bi" :class="showHeaders ? 'bi-dash' : 'bi-plus'"></i> Headers258  </button>259  <!-- Body -->260  <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleBody" v-if="data.method !== 'GET'">261  <i class="bi" :class="showBody ? 'bi-dash' : 'bi-plus'"></i> Body262  </button>263  <!-- Repeat Config -->264  <button class="btn btn-sm btn-outline-secondary" @click="toggleRepeat">265  <i class="bi" :class="showRepeat ? 'bi-dash' : 'bi-plus'"></i> Repeat266  </button>267  <!-- Headers -->268  <div v-if="showHeaders" class="mt-2">269  <div v-for="(header, index) in data.headers" :key="index" class="d-flex align-items-center mb-2">270  <input type="text" class="form-control me-2" v-model="header.key" placeholder="Header Key" style="max-width: 150px;">271  <input type="text" class="form-control me-2" v-model="header.value" placeholder="Header Value" style="max-width: 150px;">272  <button class="btn btn-sm btn-outline-danger" @click="removeHeader(index)">273  <i class="bi bi-trash"></i>274  </button>275  </div>276  <button class="btn btn-outline-primary btn-sm" @click="addHeader">277  <i class="bi bi-plus-lg"></i> Agregar Header278  </button>279  </div>280  <!-- Body -->281  <div v-if="showBody" class="mt-2">282  <label class="form-label">Body</label>283  <textarea class="form-control" rows="3" v-model="data.body" placeholder="Cuerpo de la solicitud"></textarea>284  </div>285  <!-- Repeat -->286  <div v-if="showRepeat" class="mt-2">287  <div class="d-flex align-items-center">288  <label class="form-label me-2">Intervalo (s):</label>289  <input type="number" class="form-control me-2" v-model.number="data.repeatInterval" style="max-width: 100px;">290  <label class="form-label me-2">Máximo de Intentos:</label>291  <input type="number" class="form-control me-2" v-model.number="data.repeatMaxAttempts" style="max-width: 100px;">292  </div>293  <div class="mt-2">294  <label class="form-label">Condición para Repetir:</label>295  <input type="text" class="form-control" v-model="data.repeatCondition" placeholder="Ejemplo: response.status == 404">296  </div>297  <div class="mt-2">298  <label class="form-label">Condición de Éxito:</label>299  <input type="text" class="form-control" v-model="data.successCondition" placeholder="Ejemplo: response.status == 200 || response.status == 201">300  </div>301  </div>302  </div>303  </div>304  `,305  props: ['modelValue', 'step'],306  data() {307  return {308  showHeaders: false,309  showBody: false,310  showRepeat: false,311  }312  },313  computed: {314  data: {315  get() { return this.modelValue; },316  set(value) { this.$emit('update:modelValue', value); }317  }318  },319  methods: {320  toggleDetails() {321  this.step.expanded = !this.step.expanded;322  },323  toggleHeaders() {324  this.showHeaders = !this.showHeaders;325  },326  toggleBody() {327  this.showBody = !this.showBody;328  },329  toggleRepeat() {330  this.showRepeat = !this.showRepeat;331  },332  addHeader() {333  this.data.headers.push({ key: '', value: '' });334  },335  removeHeader(index) {336  this.data.headers.splice(index, 1);337  }338  }339  };340 341  const AssertionStep = {342  template: `343  <div class="d-flex align-items-center">344  <input type="text" class="form-control" v-model="data.condition" placeholder="Condición">345  </div>346  `,347  props: ['modelValue', 'step'],348  computed: {349  data: {350  get() { return this.modelValue; },351  set(value) { this.$emit('update:modelValue', value); }352  }353  }354  };355 356  const WaitStep = {357  template: `358  <div class="d-flex align-items-center">359  <label class="form-label me-2">Esperar</label>360  <input type="number" class="form-control me-2" v-model.number="data.seconds" placeholder="Segundos" style="max-width: 100px;">361  </div>362  `,363  props: ['modelValue', 'step'],364  computed: {365  data: {366  get() { return this.modelValue; },367  set(value) { this.$emit('update:modelValue', value); }368  }369  }370  };371 372  // Instancia de Vue373  const { createApp } = Vue;374 375  createApp({376  components: {377  'variable-step': VariableStep,378  'request-step': RequestStep,379  'assertion-step': AssertionStep,380  'wait-step': WaitStep,381  // Agrega más componentes si es necesario382  },383  data() {384  return {385  steps: [],386  darkTheme: false,387  }388  },389  computed: {390  isExecutable() {391  // Verifica si hay al menos un paso para ejecutar392  return this.steps.length > 0;393  }394  },395  methods: {396  getStepComponent(type) {397  switch (type) {398  case 'variable':399  return 'variable-step';400  case 'request':401  return 'request-step';402  case 'assertion':403  return 'assertion-step';404  case 'wait':405  return 'wait-step';406  // Otros casos...407  }408  },409  getStepTypeLabel(type, method) {410  switch (type) {411  case 'variable':412  return 'VAR';413  case 'request':414  return method;415  case 'assertion':416  return 'ASSERT';417  case 'wait':418  return 'WAIT';419  // Otros casos...420  }421  },422  moveUp(index) {423  if (index > 0) {424  [this.steps[index - 1], this.steps[index]] = [this.steps[index], this.steps[index - 1]];425  }426  },427  moveDown(index) {428  if (index < this.steps.length - 1) {429  [this.steps[index], this.steps[index + 1]] = [this.steps[index + 1], this.steps[index]];430  }431  },432  removeStep(index) {433  this.steps.splice(index, 1);434  },435  addStep(type) {436  let newStep = {437  type: type,438  data: {},439  deferred: false,440  expanded: false,441  result: null,442  showResult: false,443  };444  switch (type) {445  case 'variable':446  newStep.data = { name: '', value: '' };447  break;448  case 'request':449  newStep.data = {450  method: 'GET',451  url: '',452  headers: [],453  body: '',454  repeatInterval: 0,455  repeatMaxAttempts: 1,456  repeatCondition: '',457  successCondition: '',458  };459  break;460  case 'assertion':461  newStep.data = { condition: '' };462  break;463  case 'wait':464  newStep.data = { seconds: 1 };465  break;466  // Otros casos...467  }468  this.steps.push(newStep);469  },470  async executeTest() {471  // Reiniciar resultados472  this.steps.forEach(step => {473  if (step.type === 'request') {474  step.result = null;475  step.showResult = false;476  }477  });478  // Filtrar los pasos que no son "Deferred"479  const stepsToExecute = this.steps.filter(step => !step.deferred);480  // Ejecutar los pasos secuencialmente481  for (const step of stepsToExecute) {482  const res = await this.executeStep(step);483  if (!res.success) {484  return;485  }486  }487  // Ejecutar los pasos "Deferred"488  const deferredSteps = this.steps.filter(step => step.deferred);489  for (const step of deferredSteps) {490  await this.executeStep(step);491  }492  },493  async executeStep(step) {494  switch (step.type) {495  case 'variable':496  // Aquí podrías almacenar la variable en un contexto si es necesario497  return { success: true };498  case 'request':499  return await this.executeRequest(step);500  case 'assertion':501  // Implementar lógica de aserciones si es necesario502  return { success: true };503  case 'wait':504  await new Promise(resolve => setTimeout(resolve, step.data.seconds * 1000));505  return { success: true };506  // Otros casos...507  }508  },509  async executeRequest(step) {510  const data = step.data;511  try {512  let attempts = 0;513  let maxAttempts = data.repeatMaxAttempts || 1;514  let interval = data.repeatInterval || 0;515  let conditionMet = false;516  let response;517  do {518  attempts++;519  // Configurar la solicitud520  const config = {521  method: data.method.toLowerCase(),522  url: data.url,523  headers: {},524  };525  data.headers.forEach(header => {526  config.headers[header.key] = header.value;527  });528  if (data.method !== 'GET' && data.body) {529  config.data = data.body;530  }531  // Realizar la solicitud532  response = await axios(config);533  // Evaluar condiciones534  if (data.successCondition) {535  conditionMet = eval(data.successCondition);536  } else {537  conditionMet = true;538  }539  if (!conditionMet && attempts < maxAttempts) {540  await new Promise(resolve => setTimeout(resolve, interval * 1000));541  }542  } while (!conditionMet && attempts < maxAttempts);543  // Mostrar resultado dentro del paso544  step.result = {545  success: true,546  message: 'Solicitud ejecutada exitosamente.',547  details: {548  status: response.status,549  headers: JSON.stringify(response.headers, null, 2),550  body: JSON.stringify(response.data, null, 2)551  }552  };553  return { success: true };554  } catch (error) {555  step.result = {556  success: false,557  message: 'Error al ejecutar la solicitud.',558  details: {559  status: error.response ? error.response.status : 'Sin respuesta',560  headers: error.response ? JSON.stringify(error.response.headers, null, 2) : 'No disponible',561  body: error.response ? JSON.stringify(error.response.data, null, 2) : error.message562  }563  };564  return { success: false };565  }566  },567  executeRequestStep(index) {568  const step = this.steps[index];569  // Reiniciar resultado previo570  step.result = null;571  step.showResult = false;572  this.executeRequest(step);573  },574  saveFlow() {575  const dataStr = JSON.stringify(this.steps, null, 2);576  const blob = new Blob([dataStr], { type: "application/json" });577  const url = URL.createObjectURL(blob);578  const a = document.createElement('a');579  a.href = url;580  a.download = 'flujo_de_prueba.json';581  a.click();582  URL.revokeObjectURL(url);583  },584  loadFlow() {585  document.getElementById('fileInput').click();586  },587  handleFileUpload(event) {588  const file = event.target.files[0];589  if (file) {590  const reader = new FileReader();591  reader.onload = (e) => {592  try {593  const data = JSON.parse(e.target.result);594  // Validar que el archivo contiene un array de pasos595  if (Array.isArray(data)) {596  this.steps = data;597  } else {598  alert('El archivo no es válido.');599  }600  } catch (error) {601  alert('Error al cargar el archivo: ' + error.message);602  }603  };604  reader.readAsText(file);605  // Limpiar el valor del input para permitir cargar el mismo archivo nuevamente si es necesario606  event.target.value = '';607  }608  },609  },610  mounted() {611  // Inicializar el tema según las preferencias del usuario612  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;613  if (prefersDark) {614  this.toggleTheme();615  }616  }617  }).mount('#app');618 619  // Manejo del botón de cambio de tema fuera de Vue620  document.getElementById('themeToggle').addEventListener('click', () => {621  document.body.classList.toggle('bg-dark');622  document.body.classList.toggle('text-white');623  const icon = document.getElementById('themeToggle').querySelector('i');624  icon.classList.toggle('bi-moon-fill');625  icon.classList.toggle('bi-sun-fill');626  });627  </script>628 </body>629 </html>630 


Este ShareCode tiene versiones:
  1. Plataforma de Testing Automa... (28/09/2024)
Enlace
El enlace para compartir es: