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: center;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  position: relative;35  }36  .step-type::after {37  content: '';38  position: absolute;39  left: calc(100% + 5px);40  top: 0;41  bottom: 0;42  width: 2px;43  background-color: #0d6efd;44  }45  .step-content {46  flex: 1;47  display: flex;48  align-items: center;49  }50  .step-content input,51  .step-content select,52  .step-content textarea {53  margin-right: 8px;54  }55  .step-controls {56  display: none;57  align-items: center;58  }59  .step-controls button {60  background: none;61  border: none;62  margin-left: 5px;63  color: #6c757d;64  }65  .step-controls button:hover {66  color: #000;67  }68  .add-step {69  text-align: center;70  margin: 20px 0;71  }72  .theme-toggle {73  cursor: pointer;74  }75  .execute-btn {76  background: none;77  border: none;78  color: #198754;79  font-size: 1.2em;80  }81  .execute-btn:hover {82  color: #145c32;83  }84  .result-panel {85  margin-top: 20px;86  }87  .deferred-label {88  margin-left: 10px;89  font-size: 0.9em;90  color: #6c757d;91  }92  .vertical-line {93  position: absolute;94  left: 70px;95  top: 0;96  bottom: 0;97  width: 2px;98  background-color: #0d6efd;99  }100  </style>101 </head>102 <body>103  <!-- Barra de navegación -->104  <nav class="navbar navbar-expand-lg navbar-dark bg-primary">105  <div class="container-fluid">106  <a class="navbar-brand" href="#">107  <!-- Logo -->108  <img src="https://via.placeholder.com/150x40?text=Logo" alt="Logo">109  </a>110  <span class="navbar-text text-white">111  Plataforma de Testing Automatizado de APIs112  </span>113  <button class="btn btn-secondary ms-auto theme-toggle" id="themeToggle">114  <i class="bi bi-moon-fill"></i>115  </button>116  </div>117  </nav>118 119  <!-- Contenido principal -->120  <div id="app" class="container my-4">121  <!-- Lista de pasos -->122  <div v-for="(step, index) in steps" :key="index" class="step">123  <span class="step-type" v-if="step.type === 'variable'">VAR</span>124  <span class="step-type" v-else-if="step.type === 'request'">{{ step.data.method }}</span>125  <span class="step-type" v-else-if="step.type === 'assertion'">ASSERT</span>126  <span class="step-type" v-else-if="step.type === 'wait'">WAIT</span>127  <div class="vertical-line"></div>128 129  <div class="step-content">130  <!-- Contenido según el tipo de paso -->131  <component :is="getStepComponent(step.type)" v-model="step.data"></component>132  <!-- Checkbox para "Deferred" -->133  <div v-if="step.type === 'request' || step.type === 'assertion'">134  <input type="checkbox" v-model="step.deferred" id="deferred-{{ index }}">135  <label :for="'deferred-' + index" class="deferred-label">Deferred</label>136  </div>137  </div>138  <div class="step-controls">139  <button @click="moveUp(index)" :disabled="index === 0"><i class="bi bi-arrow-up"></i></button>140  <button @click="moveDown(index)" :disabled="index === steps.length - 1"><i class="bi bi-arrow-down"></i></button>141  <button v-if="step.type === 'request'" @click="executeRequestStep(index)" class="execute-btn"><i class="bi bi-play-circle"></i></button>142  <button @click="removeStep(index)"><i class="bi bi-trash"></i></button>143  </div>144  </div>145 146  <!-- Botones para añadir nuevos pasos -->147  <div class="add-step">148  <button class="btn btn-outline-primary me-2" @click="addStep('variable')">149  <i class="bi bi-sliders"></i> Variable150  </button>151  <button class="btn btn-outline-primary me-2" @click="addStep('request')">152  <i class="bi bi-arrow-up-right-circle"></i> Solicitud HTTP153  </button>154  <button class="btn btn-outline-primary me-2" @click="addStep('assertion')">155  <i class="bi bi-check-circle"></i> Aserción156  </button>157  <button class="btn btn-outline-primary me-2" @click="addStep('wait')">158  <i class="bi bi-hourglass-split"></i> Wait159  </button>160  <!-- Agrega más botones si es necesario -->161  </div>162 163  <!-- Botón de Ejecución -->164  <div class="text-center my-4">165  <button class="btn btn-lg btn-success" @click="executeTest" :disabled="!isExecutable">166  <i class="bi bi-play-fill"></i> Ejecutar Prueba Completa167  </button>168  </div>169 170  <!-- Resultados -->171  <div class="result-panel" v-if="result">172  <div class="alert" :class="{'alert-success': result.success, 'alert-danger': !result.success}">173  <strong>Resultado:</strong> {{ result.message }}174  </div>175  <div v-if="result.details">176  <h5>Detalles de la Respuesta:</h5>177  <p><strong>Status Code:</strong> {{ result.details.status }}</p>178  <p><strong>Headers:</strong></p>179  <pre>{{ result.details.headers }}</pre>180  <p><strong>Body:</strong></p>181  <pre>{{ result.details.body }}</pre>182  </div>183  </div>184  </div>185 186  <!-- Enlaces a Vue.js y Bootstrap JS -->187  <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>188  <script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>189  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>190 191  <!-- Componentes para los pasos -->192  <script>193  const VariableStep = {194  template: `195  <div class="d-flex align-items-center">196  <input type="text" class="form-control me-2" v-model="data.name" placeholder="Nombre" style="max-width: 150px;">197  <span>=</span>198  <input type="text" class="form-control ms-2" v-model="data.value" placeholder="Valor" style="max-width: 150px;">199  </div>200  `,201  props: ['modelValue'],202  computed: {203  data: {204  get() { return this.modelValue; },205  set(value) { this.$emit('update:modelValue', value); }206  }207  }208  };209 210  const RequestStep = {211  template: `212  <div class="d-flex align-items-center flex-wrap">213  <select class="form-select me-2" v-model="data.method" style="max-width: 100px;">214  <option>GET</option>215  <option>POST</option>216  <option>PUT</option>217  <option>DELETE</option>218  </select>219  <input type="text" class="form-control me-2" v-model="data.url" placeholder="URL" style="flex: 1;">220  <!-- Botón para mostrar detalles -->221  <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleDetails">222  <i class="bi" :class="showDetails ? 'bi-chevron-up' : 'bi-chevron-down'"></i>223  </button>224  <!-- Detalles opcionales -->225  <div v-if="showDetails" class="w-100 mt-2">226  <!-- Headers -->227  <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleHeaders">228  <i class="bi" :class="showHeaders ? 'bi-dash' : 'bi-plus'"></i> Headers229  </button>230  <!-- Body -->231  <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleBody" v-if="data.method !== 'GET'">232  <i class="bi" :class="showBody ? 'bi-dash' : 'bi-plus'"></i> Body233  </button>234  <!-- Repeat Config -->235  <button class="btn btn-sm btn-outline-secondary" @click="toggleRepeat">236  <i class="bi" :class="showRepeat ? 'bi-dash' : 'bi-plus'"></i> Repeat237  </button>238  <!-- Headers -->239  <div v-if="showHeaders" class="mt-2">240  <div v-for="(header, index) in data.headers" :key="index" class="d-flex align-items-center mb-2">241  <input type="text" class="form-control me-2" v-model="header.key" placeholder="Header Key" style="max-width: 150px;">242  <input type="text" class="form-control me-2" v-model="header.value" placeholder="Header Value" style="max-width: 150px;">243  <button class="btn btn-sm btn-outline-danger" @click="removeHeader(index)">244  <i class="bi bi-trash"></i>245  </button>246  </div>247  <button class="btn btn-outline-primary btn-sm" @click="addHeader">248  <i class="bi bi-plus-lg"></i> Agregar Header249  </button>250  </div>251  <!-- Body -->252  <div v-if="showBody" class="mt-2">253  <label class="form-label">Body</label>254  <textarea class="form-control" rows="3" v-model="data.body" placeholder="Cuerpo de la solicitud"></textarea>255  </div>256  <!-- Repeat -->257  <div v-if="showRepeat" class="mt-2">258  <div class="d-flex align-items-center">259  <label class="form-label me-2">Intervalo (s):</label>260  <input type="number" class="form-control me-2" v-model.number="data.repeatInterval" style="max-width: 100px;">261  <label class="form-label me-2">Máximo de Intentos:</label>262  <input type="number" class="form-control me-2" v-model.number="data.repeatMaxAttempts" style="max-width: 100px;">263  </div>264  <div class="mt-2">265  <label class="form-label">Condición para Repetir:</label>266  <input type="text" class="form-control" v-model="data.repeatCondition" placeholder="Ejemplo: response.status == 404">267  </div>268  <div class="mt-2">269  <label class="form-label">Condición de Éxito:</label>270  <input type="text" class="form-control" v-model="data.successCondition" placeholder="Ejemplo: response.status == 200 || response.status == 201">271  </div>272  </div>273  </div>274  </div>275  `,276  props: ['modelValue'],277  data() {278  return {279  showDetails: false,280  showHeaders: false,281  showBody: false,282  showRepeat: false,283  }284  },285  computed: {286  data: {287  get() { return this.modelValue; },288  set(value) { this.$emit('update:modelValue', value); }289  }290  },291  methods: {292  toggleDetails() {293  this.showDetails = !this.showDetails;294  },295  toggleHeaders() {296  this.showHeaders = !this.showHeaders;297  },298  toggleBody() {299  this.showBody = !this.showBody;300  },301  toggleRepeat() {302  this.showRepeat = !this.showRepeat;303  },304  addHeader() {305  this.data.headers.push({ key: '', value: '' });306  },307  removeHeader(index) {308  this.data.headers.splice(index, 1);309  }310  }311  };312 313  const AssertionStep = {314  template: `315  <div class="d-flex align-items-center">316  <input type="text" class="form-control" v-model="data.condition" placeholder="Condición">317  </div>318  `,319  props: ['modelValue'],320  computed: {321  data: {322  get() { return this.modelValue; },323  set(value) { this.$emit('update:modelValue', value); }324  }325  }326  };327 328  const WaitStep = {329  template: `330  <div class="d-flex align-items-center">331  <label class="form-label me-2">Esperar</label>332  <input type="number" class="form-control me-2" v-model.number="data.seconds" placeholder="Segundos" style="max-width: 100px;">333  </div>334  `,335  props: ['modelValue'],336  computed: {337  data: {338  get() { return this.modelValue; },339  set(value) { this.$emit('update:modelValue', value); }340  }341  }342  };343 344  // Instancia de Vue345  const { createApp } = Vue;346 347  createApp({348  components: {349  'variable-step': VariableStep,350  'request-step': RequestStep,351  'assertion-step': AssertionStep,352  'wait-step': WaitStep,353  // Agrega más componentes si es necesario354  },355  data() {356  return {357  steps: [],358  result: null,359  darkTheme: false,360  }361  },362  computed: {363  isExecutable() {364  // Verifica si hay al menos un paso para ejecutar365  return this.steps.length > 0;366  }367  },368  methods: {369  getStepComponent(type) {370  switch (type) {371  case 'variable':372  return 'variable-step';373  case 'request':374  return 'request-step';375  case 'assertion':376  return 'assertion-step';377  case 'wait':378  return 'wait-step';379  // Otros casos...380  }381  },382  moveUp(index) {383  if (index > 0) {384  [this.steps[index - 1], this.steps[index]] = [this.steps[index], this.steps[index - 1]];385  }386  },387  moveDown(index) {388  if (index < this.steps.length - 1) {389  [this.steps[index], this.steps[index + 1]] = [this.steps[index + 1], this.steps[index]];390  }391  },392  removeStep(index) {393  this.steps.splice(index, 1);394  },395  addStep(type) {396  let newStep = {397  type: type,398  data: {},399  deferred: false,400  };401  switch (type) {402  case 'variable':403  newStep.data = { name: '', value: '' };404  break;405  case 'request':406  newStep.data = {407  method: 'GET',408  url: '',409  headers: [],410  body: '',411  repeatInterval: 0,412  repeatMaxAttempts: 1,413  repeatCondition: '',414  successCondition: '',415  };416  break;417  case 'assertion':418  newStep.data = { condition: '' };419  break;420  case 'wait':421  newStep.data = { seconds: 1 };422  break;423  // Otros casos...424  }425  this.steps.push(newStep);426  },427  async executeTest() {428  this.result = null;429  // Filtrar los pasos que no son "Deferred"430  const stepsToExecute = this.steps.filter(step => !step.deferred);431  // Ejecutar los pasos secuencialmente432  for (const step of stepsToExecute) {433  const res = await this.executeStep(step);434  if (!res.success) {435  this.result = res;436  return;437  }438  }439  // Ejecutar los pasos "Deferred"440  const deferredSteps = this.steps.filter(step => step.deferred);441  for (const step of deferredSteps) {442  await this.executeStep(step);443  }444  // Resultado final445  this.result = {446  success: true,447  message: 'Prueba completa ejecutada exitosamente.'448  };449  },450  async executeStep(step) {451  switch (step.type) {452  case 'variable':453  // Aquí podrías almacenar la variable en un contexto si es necesario454  return { success: true };455  case 'request':456  return await this.executeRequest(step.data);457  case 'assertion':458  // Implementar lógica de aserciones si es necesario459  return { success: true };460  case 'wait':461  await new Promise(resolve => setTimeout(resolve, step.data.seconds * 1000));462  return { success: true };463  // Otros casos...464  }465  },466  async executeRequest(data) {467  try {468  let attempts = 0;469  let maxAttempts = data.repeatMaxAttempts || 1;470  let interval = data.repeatInterval || 0;471  let conditionMet = false;472  let response;473  do {474  attempts++;475  // Configurar la solicitud476  const config = {477  method: data.method.toLowerCase(),478  url: data.url,479  headers: {},480  };481  data.headers.forEach(header => {482  config.headers[header.key] = header.value;483  });484  if (data.method !== 'GET' && data.body) {485  config.data = data.body;486  }487  // Realizar la solicitud488  response = await axios(config);489  // Evaluar condiciones490  if (data.successCondition) {491  conditionMet = eval(data.successCondition);492  } else {493  conditionMet = true;494  }495  if (!conditionMet && attempts < maxAttempts) {496  await new Promise(resolve => setTimeout(resolve, interval * 1000));497  }498  } while (!conditionMet && attempts < maxAttempts);499  // Mostrar resultado500  this.result = {501  success: true,502  message: 'Solicitud ejecutada exitosamente.',503  details: {504  status: response.status,505  headers: JSON.stringify(response.headers, null, 2),506  body: JSON.stringify(response.data, null, 2)507  }508  };509  return { success: true };510  } catch (error) {511  this.result = {512  success: false,513  message: 'Error al ejecutar la solicitud.',514  details: {515  status: error.response ? error.response.status : 'Sin respuesta',516  headers: error.response ? JSON.stringify(error.response.headers, null, 2) : 'No disponible',517  body: error.response ? JSON.stringify(error.response.data, null, 2) : error.message518  }519  };520  return { success: false };521  }522  },523  executeRequestStep(index) {524  const step = this.steps[index];525  this.executeRequest(step.data);526  }527  },528  mounted() {529  // Inicializar el tema según las preferencias del usuario530  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;531  if (prefersDark) {532  this.toggleTheme();533  }534  }535  }).mount('#app');536 537  // Manejo del botón de cambio de tema fuera de Vue538  document.getElementById('themeToggle').addEventListener('click', () => {539  document.body.classList.toggle('bg-dark');540  document.body.classList.toggle('text-white');541  const icon = document.getElementById('themeToggle').querySelector('i');542  icon.classList.toggle('bi-moon-fill');543  icon.classList.toggle('bi-sun-fill');544  });545  </script>546 </body>547 </html>548 


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