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  </style>102 </head>103 <body>104  <!-- Barra de navegación -->105  <nav class="navbar navbar-expand-lg navbar-dark bg-primary">106  <div class="container-fluid">107  <a class="navbar-brand" href="#">108  <!-- Logo -->109  <img src="https://via.placeholder.com/150x40?text=Logo" alt="Logo">110  </a>111  <span class="navbar-text text-white">112  Plataforma de Testing Automatizado de APIs113  </span>114  <button class="btn btn-secondary ms-auto theme-toggle" id="themeToggle">115  <i class="bi bi-moon-fill"></i>116  </button>117  </div>118  </nav>119 120  <!-- Contenido principal -->121  <div id="app" class="container my-4">122  <!-- Lista de pasos -->123  <div v-for="(step, index) in steps" :key="index" class="step">124  <span class="step-type">{{ getStepTypeLabel(step.type, step.data.method) }}</span>125 126  <div class="step-content">127  <div class="main-content">128  <!-- Contenido según el tipo de paso -->129  <component :is="getStepComponent(step.type)" v-model="step.data" :step="step"></component>130  <!-- Checkbox para "Deferred" -->131  <div v-if="step.type === 'request' || step.type === 'assertion'">132  <input type="checkbox" v-model="step.deferred" :id="'deferred-' + index">133  <label :for="'deferred-' + index" class="deferred-label">Deferred</label>134  </div>135  </div>136  <!-- Resultados dentro del bloque de solicitud -->137  <div v-if="step.type === 'request' && step.result" class="result-panel">138  <button class="result-toggle" @click="step.showResult = !step.showResult">139  <i class="bi" :class="step.showResult ? 'bi-eye-slash' : 'bi-eye'"></i>140  {{ step.showResult ? 'Ocultar' : 'Mostrar' }} Resultado141  </button>142  <div v-if="step.showResult">143  <div class="alert mt-2" :class="{'alert-success': step.result.success, 'alert-danger': !step.result.success}">144  <strong>Resultado:</strong> {{ step.result.message }}145  </div>146  <div v-if="step.result.details">147  <p><strong>Status Code:</strong> {{ step.result.details.status }}</p>148  <p><strong>Headers:</strong></p>149  <pre>{{ step.result.details.headers }}</pre>150  <p><strong>Body:</strong></p>151  <pre style="max-height: 200px; overflow-y: auto;">{{ step.result.details.body }}</pre>152  </div>153  </div>154  </div>155  </div>156  <div class="step-controls">157  <button @click="moveUp(index)" :disabled="index === 0"><i class="bi bi-arrow-up"></i></button>158  <button @click="moveDown(index)" :disabled="index === steps.length - 1"><i class="bi bi-arrow-down"></i></button>159  <button v-if="step.type === 'request'" @click="executeRequestStep(index)" class="execute-btn"><i class="bi bi-play-circle"></i></button>160  <button @click="removeStep(index)"><i class="bi bi-trash"></i></button>161  </div>162  </div>163 164  <!-- Botones para añadir nuevos pasos -->165  <div class="add-step">166  <button class="btn btn-outline-primary me-2" @click="addStep('variable')">167  <i class="bi bi-sliders"></i> Variable168  </button>169  <button class="btn btn-outline-primary me-2" @click="addStep('request')">170  <i class="bi bi-arrow-up-right-circle"></i> Solicitud HTTP171  </button>172  <button class="btn btn-outline-primary me-2" @click="addStep('assertion')">173  <i class="bi bi-check-circle"></i> Aserción174  </button>175  <button class="btn btn-outline-primary me-2" @click="addStep('wait')">176  <i class="bi bi-hourglass-split"></i> Wait177  </button>178  <!-- Agrega más botones si es necesario -->179  </div>180 181  <!-- Botón de Ejecución -->182  <div class="text-center my-4">183  <button class="btn btn-lg btn-success" @click="executeTest" :disabled="!isExecutable">184  <i class="bi bi-play-fill"></i> Ejecutar Prueba Completa185  </button>186  </div>187  </div>188 189  <!-- Enlaces a Vue.js, Axios y Bootstrap JS -->190  <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>191  <script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>192  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>193 194  <!-- Componentes para los pasos -->195  <script>196  const VariableStep = {197  template: `198  <div class="d-flex align-items-center">199  <input type="text" class="form-control me-2" v-model="data.name" placeholder="Nombre" style="max-width: 150px;">200  <span>=</span>201  <input type="text" class="form-control ms-2" v-model="data.value" placeholder="Valor" style="max-width: 150px;">202  </div>203  `,204  props: ['modelValue', 'step'],205  computed: {206  data: {207  get() { return this.modelValue; },208  set(value) { this.$emit('update:modelValue', value); }209  }210  }211  };212 213  const RequestStep = {214  template: `215  <div>216  <div class="d-flex align-items-center flex-wrap">217  <select class="form-select me-2" v-model="data.method" style="max-width: 100px;">218  <option>GET</option>219  <option>POST</option>220  <option>PUT</option>221  <option>DELETE</option>222  </select>223  <input type="text" class="form-control me-2" v-model="data.url" placeholder="URL" style="flex: 1;">224  <!-- Botón para mostrar detalles -->225  <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleDetails">226  <i class="bi" :class="step.expanded ? 'bi-chevron-up' : 'bi-chevron-down'"></i>227  </button>228  </div>229  <!-- Detalles opcionales -->230  <div v-if="step.expanded" class="details mt-2">231  <!-- Headers -->232  <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleHeaders">233  <i class="bi" :class="showHeaders ? 'bi-dash' : 'bi-plus'"></i> Headers234  </button>235  <!-- Body -->236  <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleBody" v-if="data.method !== 'GET'">237  <i class="bi" :class="showBody ? 'bi-dash' : 'bi-plus'"></i> Body238  </button>239  <!-- Repeat Config -->240  <button class="btn btn-sm btn-outline-secondary" @click="toggleRepeat">241  <i class="bi" :class="showRepeat ? 'bi-dash' : 'bi-plus'"></i> Repeat242  </button>243  <!-- Headers -->244  <div v-if="showHeaders" class="mt-2">245  <div v-for="(header, index) in data.headers" :key="index" class="d-flex align-items-center mb-2">246  <input type="text" class="form-control me-2" v-model="header.key" placeholder="Header Key" style="max-width: 150px;">247  <input type="text" class="form-control me-2" v-model="header.value" placeholder="Header Value" style="max-width: 150px;">248  <button class="btn btn-sm btn-outline-danger" @click="removeHeader(index)">249  <i class="bi bi-trash"></i>250  </button>251  </div>252  <button class="btn btn-outline-primary btn-sm" @click="addHeader">253  <i class="bi bi-plus-lg"></i> Agregar Header254  </button>255  </div>256  <!-- Body -->257  <div v-if="showBody" class="mt-2">258  <label class="form-label">Body</label>259  <textarea class="form-control" rows="3" v-model="data.body" placeholder="Cuerpo de la solicitud"></textarea>260  </div>261  <!-- Repeat -->262  <div v-if="showRepeat" class="mt-2">263  <div class="d-flex align-items-center">264  <label class="form-label me-2">Intervalo (s):</label>265  <input type="number" class="form-control me-2" v-model.number="data.repeatInterval" style="max-width: 100px;">266  <label class="form-label me-2">Máximo de Intentos:</label>267  <input type="number" class="form-control me-2" v-model.number="data.repeatMaxAttempts" style="max-width: 100px;">268  </div>269  <div class="mt-2">270  <label class="form-label">Condición para Repetir:</label>271  <input type="text" class="form-control" v-model="data.repeatCondition" placeholder="Ejemplo: response.status == 404">272  </div>273  <div class="mt-2">274  <label class="form-label">Condición de Éxito:</label>275  <input type="text" class="form-control" v-model="data.successCondition" placeholder="Ejemplo: response.status == 200 || response.status == 201">276  </div>277  </div>278  </div>279  </div>280  `,281  props: ['modelValue', 'step'],282  data() {283  return {284  showHeaders: false,285  showBody: false,286  showRepeat: false,287  }288  },289  computed: {290  data: {291  get() { return this.modelValue; },292  set(value) { this.$emit('update:modelValue', value); }293  }294  },295  methods: {296  toggleDetails() {297  this.step.expanded = !this.step.expanded;298  },299  toggleHeaders() {300  this.showHeaders = !this.showHeaders;301  },302  toggleBody() {303  this.showBody = !this.showBody;304  },305  toggleRepeat() {306  this.showRepeat = !this.showRepeat;307  },308  addHeader() {309  this.data.headers.push({ key: '', value: '' });310  },311  removeHeader(index) {312  this.data.headers.splice(index, 1);313  }314  }315  };316 317  const AssertionStep = {318  template: `319  <div class="d-flex align-items-center">320  <input type="text" class="form-control" v-model="data.condition" placeholder="Condición">321  </div>322  `,323  props: ['modelValue', 'step'],324  computed: {325  data: {326  get() { return this.modelValue; },327  set(value) { this.$emit('update:modelValue', value); }328  }329  }330  };331 332  const WaitStep = {333  template: `334  <div class="d-flex align-items-center">335  <label class="form-label me-2">Esperar</label>336  <input type="number" class="form-control me-2" v-model.number="data.seconds" placeholder="Segundos" style="max-width: 100px;">337  </div>338  `,339  props: ['modelValue', 'step'],340  computed: {341  data: {342  get() { return this.modelValue; },343  set(value) { this.$emit('update:modelValue', value); }344  }345  }346  };347 348  // Instancia de Vue349  const { createApp } = Vue;350 351  createApp({352  components: {353  'variable-step': VariableStep,354  'request-step': RequestStep,355  'assertion-step': AssertionStep,356  'wait-step': WaitStep,357  // Agrega más componentes si es necesario358  },359  data() {360  return {361  steps: [],362  darkTheme: false,363  }364  },365  computed: {366  isExecutable() {367  // Verifica si hay al menos un paso para ejecutar368  return this.steps.length > 0;369  }370  },371  methods: {372  getStepComponent(type) {373  switch (type) {374  case 'variable':375  return 'variable-step';376  case 'request':377  return 'request-step';378  case 'assertion':379  return 'assertion-step';380  case 'wait':381  return 'wait-step';382  // Otros casos...383  }384  },385  getStepTypeLabel(type, method) {386  switch (type) {387  case 'variable':388  return 'VAR';389  case 'request':390  return method;391  case 'assertion':392  return 'ASSERT';393  case 'wait':394  return 'WAIT';395  // Otros casos...396  }397  },398  moveUp(index) {399  if (index > 0) {400  [this.steps[index - 1], this.steps[index]] = [this.steps[index], this.steps[index - 1]];401  }402  },403  moveDown(index) {404  if (index < this.steps.length - 1) {405  [this.steps[index], this.steps[index + 1]] = [this.steps[index + 1], this.steps[index]];406  }407  },408  removeStep(index) {409  this.steps.splice(index, 1);410  },411  addStep(type) {412  let newStep = {413  type: type,414  data: {},415  deferred: false,416  expanded: false,417  result: null,418  showResult: false,419  };420  switch (type) {421  case 'variable':422  newStep.data = { name: '', value: '' };423  break;424  case 'request':425  newStep.data = {426  method: 'GET',427  url: '',428  headers: [],429  body: '',430  repeatInterval: 0,431  repeatMaxAttempts: 1,432  repeatCondition: '',433  successCondition: '',434  };435  break;436  case 'assertion':437  newStep.data = { condition: '' };438  break;439  case 'wait':440  newStep.data = { seconds: 1 };441  break;442  // Otros casos...443  }444  this.steps.push(newStep);445  },446  async executeTest() {447  // Reiniciar resultados448  this.steps.forEach(step => {449  if (step.type === 'request') {450  step.result = null;451  step.showResult = false;452  }453  });454  // Filtrar los pasos que no son "Deferred"455  const stepsToExecute = this.steps.filter(step => !step.deferred);456  // Ejecutar los pasos secuencialmente457  for (const step of stepsToExecute) {458  const res = await this.executeStep(step);459  if (!res.success) {460  return;461  }462  }463  // Ejecutar los pasos "Deferred"464  const deferredSteps = this.steps.filter(step => step.deferred);465  for (const step of deferredSteps) {466  await this.executeStep(step);467  }468  },469  async executeStep(step) {470  switch (step.type) {471  case 'variable':472  // Aquí podrías almacenar la variable en un contexto si es necesario473  return { success: true };474  case 'request':475  return await this.executeRequest(step);476  case 'assertion':477  // Implementar lógica de aserciones si es necesario478  return { success: true };479  case 'wait':480  await new Promise(resolve => setTimeout(resolve, step.data.seconds * 1000));481  return { success: true };482  // Otros casos...483  }484  },485  async executeRequest(step) {486  const data = step.data;487  try {488  let attempts = 0;489  let maxAttempts = data.repeatMaxAttempts || 1;490  let interval = data.repeatInterval || 0;491  let conditionMet = false;492  let response;493  do {494  attempts++;495  // Configurar la solicitud496  const config = {497  method: data.method.toLowerCase(),498  url: data.url,499  headers: {},500  };501  data.headers.forEach(header => {502  config.headers[header.key] = header.value;503  });504  if (data.method !== 'GET' && data.body) {505  config.data = data.body;506  }507  // Realizar la solicitud508  response = await axios(config);509  // Evaluar condiciones510  if (data.successCondition) {511  conditionMet = eval(data.successCondition);512  } else {513  conditionMet = true;514  }515  if (!conditionMet && attempts < maxAttempts) {516  await new Promise(resolve => setTimeout(resolve, interval * 1000));517  }518  } while (!conditionMet && attempts < maxAttempts);519  // Mostrar resultado dentro del paso520  step.result = {521  success: true,522  message: 'Solicitud ejecutada exitosamente.',523  details: {524  status: response.status,525  headers: JSON.stringify(response.headers, null, 2),526  body: JSON.stringify(response.data, null, 2)527  }528  };529  return { success: true };530  } catch (error) {531  step.result = {532  success: false,533  message: 'Error al ejecutar la solicitud.',534  details: {535  status: error.response ? error.response.status : 'Sin respuesta',536  headers: error.response ? JSON.stringify(error.response.headers, null, 2) : 'No disponible',537  body: error.response ? JSON.stringify(error.response.data, null, 2) : error.message538  }539  };540  return { success: false };541  }542  },543  executeRequestStep(index) {544  const step = this.steps[index];545  // Reiniciar resultado previo546  step.result = null;547  step.showResult = false;548  this.executeRequest(step);549  }550  },551  mounted() {552  // Inicializar el tema según las preferencias del usuario553  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;554  if (prefersDark) {555  this.toggleTheme();556  }557  }558  }).mount('#app');559 560  // Manejo del botón de cambio de tema fuera de Vue561  document.getElementById('themeToggle').addEventListener('click', () => {562  document.body.classList.toggle('bg-dark');563  document.body.classList.toggle('text-white');564  const icon = document.getElementById('themeToggle').querySelector('i');565  icon.classList.toggle('bi-moon-fill');566  icon.classList.toggle('bi-sun-fill');567  });568  </script>569 </body>570 </html>571 


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