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


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