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 .block {17 border: 1px solid #dee2e6;18 border-radius: 5px;19 margin-bottom: 10px;20 overflow: hidden;21 }22 .block-header {23 background-color: #f8f9fa;24 padding: 8px 12px;25 display: flex;26 align-items: center;27 justify-content: space-between;28 }29 .block-body {30 padding: 12px;31 }32 .block-controls button {33 background: none;34 border: none;35 margin-left: 5px;36 color: #6c757d;37 }38 .block-controls button:hover {39 color: #000;40 }41 .add-block {42 text-align: center;43 margin: 20px 0;44 }45 .theme-toggle {46 cursor: pointer;47 }48 .var-label {49 font-weight: bold;50 color: #0d6efd;51 margin-right: 10px;52 }53 .execute-btn {54 background: none;55 border: none;56 color: #198754;57 font-size: 1.2em;58 }59 .execute-btn:hover {60 color: #145c32;61 }62 </style>63 </head>64 <body>65 <!-- Barra de navegación -->66 <nav class="navbar navbar-expand-lg navbar-dark bg-primary">67 <div class="container-fluid">68 <a class="navbar-brand" href="#">69 <!-- Logo -->70 <img src="https://via.placeholder.com/150x40?text=Logo" alt="Logo">71 </a>72 <span class="navbar-text text-white">73 Plataforma de Testing Automatizado de APIs74 </span>75 <button class="btn btn-secondary ms-auto theme-toggle" id="themeToggle">76 <i class="bi bi-moon-fill"></i>77 </button>78 </div>79 </nav>80 81 <!-- Contenido principal -->82 <div id="app" class="container my-4">83 <!-- Lista de bloques -->84 <div v-for="(block, index) in blocks" :key="index" class="block">85 <div class="block-header">86 <div>87 <i :class="getBlockIcon(block.type)"></i>88 <span v-if="block.type === 'variable'" class="var-label">VAR</span>89 <strong v-html="getBlockTitle(block)"></strong>90 </div>91 <div class="block-controls">92 <button @click="moveUp(index)" :disabled="index === 0"><i class="bi bi-arrow-up"></i></button>93 <button @click="moveDown(index)" :disabled="index === blocks.length - 1"><i class="bi bi-arrow-down"></i></button>94 <button v-if="block.type === 'request'" @click="executeRequestBlock(index)" class="execute-btn"><i class="bi bi-play-circle"></i></button>95 <button @click="toggleBlock(index)"><i :class="block.expanded ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i></button>96 <button @click="removeBlock(index)"><i class="bi bi-trash"></i></button>97 </div>98 </div>99 <div class="block-body" v-if="block.expanded">100 <!-- Contenido dinámico según el tipo de bloque -->101 <component :is="getBlockComponent(block.type)" v-model="block.data"></component>102 </div>103 </div>104 105 <!-- Botones para añadir nuevos bloques -->106 <div class="add-block">107 <button class="btn btn-outline-primary me-2" @click="addBlock('variable')">108 <i class="bi bi-sliders"></i> Variable109 </button>110 <button class="btn btn-outline-primary me-2" @click="addBlock('request')">111 <i class="bi bi-arrow-up-right-circle"></i> Solicitud HTTP112 </button>113 <button class="btn btn-outline-primary me-2" @click="addBlock('assertion')">114 <i class="bi bi-check-circle"></i> Aserción115 </button>116 <!-- Agrega más botones si es necesario -->117 </div>118 119 <!-- Botón de Ejecución -->120 <div class="text-center my-4">121 <button class="btn btn-lg btn-success" @click="executeTest" :disabled="!isExecutable">122 <i class="bi bi-play-fill"></i> Ejecutar Prueba Completa123 </button>124 </div>125 126 <!-- Resultados -->127 <div class="block" v-if="result">128 <div class="block-header" :class="{'bg-success text-white': result.success, 'bg-danger text-white': !result.success}">129 <strong>Resultado</strong>130 </div>131 <div class="block-body">132 <p><strong>STATUS:</strong> {{ result.status }}</p>133 <p><strong>Mensaje:</strong> {{ result.message }}</p>134 </div>135 </div>136 </div>137 138 <!-- Enlaces a Vue.js y Bootstrap JS -->139 <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>140 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>141 142 <!-- Componentes para los bloques -->143 <script>144 const VariableBlock = {145 template: `146 <div class="d-flex align-items-center">147 <span class="var-label">VAR</span>148 <input type="text" class="form-control me-2" v-model="data.name" placeholder="Nombre" style="max-width: 200px;">149 <input type="text" class="form-control" v-model="data.value" placeholder="Valor" style="max-width: 200px;">150 </div>151 `,152 props: ['modelValue'],153 computed: {154 data: {155 get() { return this.modelValue; },156 set(value) { this.$emit('update:modelValue', value); }157 }158 }159 };160 161 const RequestBlock = {162 template: `163 <div>164 <!-- Método y URL en una sola línea -->165 <div class="d-flex align-items-center">166 <select class="form-select me-2" v-model="data.method" style="max-width: 100px;">167 <option>GET</option>168 <option>POST</option>169 <option>PUT</option>170 <option>DELETE</option>171 </select>172 <input type="text" class="form-control" v-model="data.url" placeholder="URL">173 </div>174 <!-- Botones para Headers y Body -->175 <div class="mt-2">176 <button class="btn btn-sm btn-outline-secondary me-2" @click="toggleHeaders">177 <i class="bi" :class="showHeaders ? 'bi-dash' : 'bi-plus'"></i> Headers178 </button>179 <button class="btn btn-sm btn-outline-secondary" @click="toggleBody" v-if="data.method !== 'GET'">180 <i class="bi" :class="showBody ? 'bi-dash' : 'bi-plus'"></i> Body181 </button>182 </div>183 <!-- Headers -->184 <div v-if="showHeaders" class="mt-2">185 <div v-for="(header, index) in data.headers" :key="index" class="row g-2 mb-2 align-items-center">186 <div class="col-md-5">187 <input type="text" class="form-control" v-model="header.key" placeholder="Header Key">188 </div>189 <div class="col-md-5">190 <input type="text" class="form-control" v-model="header.value" placeholder="Header Value">191 </div>192 <div class="col-md-2">193 <button class="btn btn-outline-danger w-100" @click="removeHeader(index)">194 <i class="bi bi-trash"></i>195 </button>196 </div>197 </div>198 <button class="btn btn-outline-primary add-button" @click="addHeader">199 <i class="bi bi-plus-lg"></i> Agregar Header200 </button>201 </div>202 <!-- Body -->203 <div v-if="showBody" class="mt-2">204 <label class="form-label">Body</label>205 <textarea class="form-control" rows="3" v-model="data.body" placeholder="Cuerpo de la solicitud"></textarea>206 </div>207 </div>208 `,209 props: ['modelValue'],210 data() {211 return {212 showHeaders: false,213 showBody: false,214 }215 },216 computed: {217 data: {218 get() { return this.modelValue; },219 set(value) { this.$emit('update:modelValue', value); }220 }221 },222 methods: {223 toggleHeaders() {224 this.showHeaders = !this.showHeaders;225 },226 toggleBody() {227 this.showBody = !this.showBody;228 },229 addHeader() {230 this.data.headers.push({ key: '', value: '' });231 },232 removeHeader(index) {233 this.data.headers.splice(index, 1);234 }235 }236 };237 238 const AssertionBlock = {239 template: `240 <div class="d-flex align-items-center">241 <span class="me-2"><i class="bi bi-check2-circle"></i> Aserción:</span>242 <input type="text" class="form-control" v-model="data.condition" placeholder="Condición">243 </div>244 `,245 props: ['modelValue'],246 computed: {247 data: {248 get() { return this.modelValue; },249 set(value) { this.$emit('update:modelValue', value); }250 }251 }252 };253 254 // Instancia de Vue255 const { createApp } = Vue;256 257 createApp({258 components: {259 'variable-block': VariableBlock,260 'request-block': RequestBlock,261 'assertion-block': AssertionBlock,262 // Agrega más componentes según sea necesario263 },264 data() {265 return {266 blocks: [],267 result: null,268 darkTheme: false,269 }270 },271 computed: {272 isExecutable() {273 // Verifica si hay al menos un bloque para ejecutar274 return this.blocks.length > 0;275 }276 },277 methods: {278 getBlockTitle(block) {279 switch (block.type) {280 case 'variable':281 return `${block.data.name} = ${block.data.value}`;282 case 'request':283 return `${block.data.method} ${block.data.url}`;284 case 'assertion':285 return `${block.data.condition}`;286 // Otros casos...287 }288 },289 getBlockIcon(type) {290 switch (type) {291 case 'variable':292 return 'bi bi-sliders';293 case 'request':294 return 'bi bi-arrow-up-right-circle';295 case 'assertion':296 return 'bi bi-check-circle';297 // Otros casos...298 }299 },300 getBlockComponent(type) {301 switch (type) {302 case 'variable':303 return 'variable-block';304 case 'request':305 return 'request-block';306 case 'assertion':307 return 'assertion-block';308 // Otros casos...309 }310 },311 toggleBlock(index) {312 this.blocks[index].expanded = !this.blocks[index].expanded;313 },314 moveUp(index) {315 if (index > 0) {316 [this.blocks[index - 1], this.blocks[index]] = [this.blocks[index], this.blocks[index - 1]];317 }318 },319 moveDown(index) {320 if (index < this.blocks.length - 1) {321 [this.blocks[index], this.blocks[index + 1]] = [this.blocks[index + 1], this.blocks[index]];322 }323 },324 removeBlock(index) {325 this.blocks.splice(index, 1);326 },327 addBlock(type) {328 let newBlock = {329 type: type,330 expanded: true,331 data: {}332 };333 switch (type) {334 case 'variable':335 newBlock.data = { name: '', value: '' };336 break;337 case 'request':338 newBlock.data = { method: 'GET', url: '', headers: [], body: '' };339 break;340 case 'assertion':341 newBlock.data = { condition: '' };342 break;343 // Otros casos...344 }345 this.blocks.push(newBlock);346 },347 executeTest() {348 // Simulación de ejecución de prueba completa349 this.result = {350 success: true,351 status: 'COMPLETED',352 message: 'Prueba completa ejecutada exitosamente.'353 };354 // Animación de scroll al resultado355 this.$nextTick(() => {356 const resultBlock = document.querySelector('.block:last-child');357 resultBlock.scrollIntoView({ behavior: 'smooth' });358 });359 },360 executeRequestBlock(index) {361 // Simulación de ejecución individual del bloque Request362 alert(`Ejecutando solicitud: ${this.blocks[index].data.method} ${this.blocks[index].data.url}`);363 // Aquí podrías implementar la lógica para ejecutar solo este bloque y mostrar resultados364 }365 },366 mounted() {367 // Inicializar el tema según las preferencias del usuario368 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;369 if (prefersDark) {370 this.toggleTheme();371 }372 }373 }).mount('#app');374 375 // Manejo del botón de cambio de tema fuera de Vue376 document.getElementById('themeToggle').addEventListener('click', () => {377 document.body.classList.toggle('bg-dark');378 document.body.classList.toggle('text-white');379 const icon = document.getElementById('themeToggle').querySelector('i');380 icon.classList.toggle('bi-moon-fill');381 icon.classList.toggle('bi-sun-fill');382 });383 </script>384 </body>385 </html>386
Enlace
El enlace para compartir es: