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: 15px;20 overflow: hidden;21 }22 .block-header {23 background-color: #f8f9fa;24 padding: 10px 15px;25 display: flex;26 align-items: center;27 justify-content: space-between;28 }29 .block-body {30 padding: 15px;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 </style>49 </head>50 <body>51 <!-- Barra de navegación -->52 <nav class="navbar navbar-expand-lg navbar-dark bg-primary">53 <div class="container-fluid">54 <a class="navbar-brand" href="#">55 <!-- Logo -->56 <img src="https://via.placeholder.com/150x40?text=Logo" alt="Logo">57 </a>58 <span class="navbar-text text-white">59 Plataforma de Testing Automatizado de APIs60 </span>61 <button class="btn btn-secondary ms-auto theme-toggle" id="themeToggle">62 <i class="bi bi-moon-fill"></i>63 </button>64 </div>65 </nav>66 67 <!-- Contenido principal -->68 <div id="app" class="container my-4">69 <!-- Lista de bloques -->70 <div v-for="(block, index) in blocks" :key="index" class="block">71 <div class="block-header">72 <div>73 <i :class="getBlockIcon(block.type)"></i>74 <strong>{{ getBlockTitle(block) }}</strong>75 </div>76 <div class="block-controls">77 <button @click="moveUp(index)" :disabled="index === 0"><i class="bi bi-arrow-up"></i></button>78 <button @click="moveDown(index)" :disabled="index === blocks.length - 1"><i class="bi bi-arrow-down"></i></button>79 <button @click="toggleBlock(index)"><i :class="block.expanded ? 'bi bi-chevron-up' : 'bi bi-chevron-down'"></i></button>80 <button @click="removeBlock(index)"><i class="bi bi-trash"></i></button>81 </div>82 </div>83 <div class="block-body" v-if="block.expanded">84 <!-- Contenido dinámico según el tipo de bloque -->85 <component :is="getBlockComponent(block.type)" v-model="block.data"></component>86 </div>87 </div>88 89 <!-- Botón para agregar nuevos bloques -->90 <div class="add-block">91 <button class="btn btn-outline-primary" @click="showAddBlockModal">92 <i class="bi bi-plus-circle"></i> Añadir Paso93 </button>94 </div>95 96 <!-- Modal para seleccionar el tipo de bloque a agregar -->97 <div class="modal fade" id="addBlockModal" tabindex="-1" aria-labelledby="addBlockModalLabel" aria-hidden="true">98 <div class="modal-dialog">99 <div class="modal-content">100 <div class="modal-header">101 <h5 class="modal-title" id="addBlockModalLabel">Añadir Nuevo Paso</h5>102 <button type="button" class="btn-close" @click="hideAddBlockModal"></button>103 </div>104 <div class="modal-body">105 <p>Selecciona el tipo de paso que deseas agregar:</p>106 <div class="list-group">107 <button class="list-group-item list-group-item-action" @click="addBlock('variable')">108 <i class="bi bi-sliders"></i> Variable109 </button>110 <button class="list-group-item list-group-item-action" @click="addBlock('request')">111 <i class="bi bi-arrow-up-right-circle"></i> Solicitud HTTP112 </button>113 <button class="list-group-item list-group-item-action" @click="addBlock('assertion')">114 <i class="bi bi-check-circle"></i> Aserción115 </button>116 <!-- Agrega más opciones si es necesario -->117 </div>118 </div>119 </div>120 </div>121 </div>122 123 <!-- Botón de Ejecución -->124 <div class="text-center my-4">125 <button class="btn btn-lg btn-success" @click="executeTest" :disabled="!isExecutable">126 <i class="bi bi-play-fill"></i> Ejecutar Prueba127 </button>128 </div>129 130 <!-- Resultados -->131 <div class="block" v-if="result">132 <div class="block-header" :class="{'bg-success text-white': result.success, 'bg-danger text-white': !result.success}">133 <strong>Resultado</strong>134 </div>135 <div class="block-body">136 <p><strong>STATUS:</strong> {{ result.status }}</p>137 <p><strong>Mensaje:</strong> {{ result.message }}</p>138 </div>139 </div>140 </div>141 142 <!-- Enlaces a Vue.js y Bootstrap JS -->143 <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.prod.js"></script>144 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>145 146 <!-- Componentes para los bloques -->147 <script>148 const VariableBlock = {149 template: `150 <div>151 <div class="row g-3">152 <div class="col-md-6">153 <label class="form-label">Nombre de la Variable</label>154 <input type="text" class="form-control" v-model="data.name">155 </div>156 <div class="col-md-6">157 <label class="form-label">Valor</label>158 <input type="text" class="form-control" v-model="data.value">159 </div>160 </div>161 </div>162 `,163 props: ['modelValue'],164 computed: {165 data: {166 get() { return this.modelValue; },167 set(value) { this.$emit('update:modelValue', value); }168 }169 }170 };171 172 const RequestBlock = {173 template: `174 <div>175 <!-- Método y URL -->176 <div class="row g-3 align-items-end">177 <div class="col-md-3">178 <label class="form-label">Método HTTP</label>179 <select class="form-select" v-model="data.method">180 <option>GET</option>181 <option>POST</option>182 <option>PUT</option>183 <option>DELETE</option>184 </select>185 </div>186 <div class="col-md-9">187 <label class="form-label">URL</label>188 <input type="text" class="form-control" v-model="data.url">189 </div>190 </div>191 <!-- Headers y Body -->192 <div class="mt-3">193 <button class="btn btn-sm btn-outline-secondary" @click="toggleHeaders">194 <i class="bi" :class="showHeaders ? 'bi-dash' : 'bi-plus'"></i> Headers195 </button>196 <button class="btn btn-sm btn-outline-secondary ms-2" @click="toggleBody" v-if="data.method !== 'GET'">197 <i class="bi" :class="showBody ? 'bi-dash' : 'bi-plus'"></i> Body198 </button>199 </div>200 <!-- Headers -->201 <div v-if="showHeaders" class="mt-3">202 <div v-for="(header, index) in data.headers" :key="index" class="row g-2 mb-2 align-items-center">203 <div class="col-md-5">204 <input type="text" class="form-control" v-model="header.key" placeholder="Header Key">205 </div>206 <div class="col-md-5">207 <input type="text" class="form-control" v-model="header.value" placeholder="Header Value">208 </div>209 <div class="col-md-2">210 <button class="btn btn-outline-danger w-100" @click="removeHeader(index)">211 <i class="bi bi-trash"></i>212 </button>213 </div>214 </div>215 <button class="btn btn-outline-primary add-button" @click="addHeader">216 <i class="bi bi-plus-lg"></i> Agregar Header217 </button>218 </div>219 <!-- Body -->220 <div v-if="showBody" class="mt-3">221 <label class="form-label">Body</label>222 <textarea class="form-control" rows="5" v-model="data.body" placeholder="Cuerpo de la solicitud"></textarea>223 </div>224 </div>225 `,226 props: ['modelValue'],227 data() {228 return {229 showHeaders: false,230 showBody: false,231 }232 },233 computed: {234 data: {235 get() { return this.modelValue; },236 set(value) { this.$emit('update:modelValue', value); }237 }238 },239 methods: {240 toggleHeaders() {241 this.showHeaders = !this.showHeaders;242 },243 toggleBody() {244 this.showBody = !this.showBody;245 },246 addHeader() {247 this.data.headers.push({ key: '', value: '' });248 },249 removeHeader(index) {250 this.data.headers.splice(index, 1);251 }252 }253 };254 255 const AssertionBlock = {256 template: `257 <div>258 <label class="form-label">Condición de Aserción</label>259 <input type="text" class="form-control" v-model="data.condition" placeholder="Ejemplo: response.status == 200">260 </div>261 `,262 props: ['modelValue'],263 computed: {264 data: {265 get() { return this.modelValue; },266 set(value) { this.$emit('update:modelValue', value); }267 }268 }269 };270 271 // Instancia de Vue272 const { createApp } = Vue;273 274 createApp({275 components: {276 'variable-block': VariableBlock,277 'request-block': RequestBlock,278 'assertion-block': AssertionBlock,279 // Agrega más componentes según sea necesario280 },281 data() {282 return {283 blocks: [],284 result: null,285 darkTheme: false,286 }287 },288 computed: {289 isExecutable() {290 // Verifica si hay al menos un bloque para ejecutar291 return this.blocks.length > 0;292 }293 },294 methods: {295 getBlockTitle(block) {296 switch (block.type) {297 case 'variable':298 return `Set Variable: ${block.data.name} = ${block.data.value}`;299 case 'request':300 return `${block.data.method} ${block.data.url}`;301 case 'assertion':302 return `Aserción: ${block.data.condition}`;303 // Otros casos...304 }305 },306 getBlockIcon(type) {307 switch (type) {308 case 'variable':309 return 'bi bi-sliders';310 case 'request':311 return 'bi bi-arrow-up-right-circle';312 case 'assertion':313 return 'bi bi-check-circle';314 // Otros casos...315 }316 },317 getBlockComponent(type) {318 switch (type) {319 case 'variable':320 return 'variable-block';321 case 'request':322 return 'request-block';323 case 'assertion':324 return 'assertion-block';325 // Otros casos...326 }327 },328 toggleBlock(index) {329 this.blocks[index].expanded = !this.blocks[index].expanded;330 },331 moveUp(index) {332 if (index > 0) {333 [this.blocks[index - 1], this.blocks[index]] = [this.blocks[index], this.blocks[index - 1]];334 }335 },336 moveDown(index) {337 if (index < this.blocks.length - 1) {338 [this.blocks[index], this.blocks[index + 1]] = [this.blocks[index + 1], this.blocks[index]];339 }340 },341 removeBlock(index) {342 this.blocks.splice(index, 1);343 },344 showAddBlockModal() {345 const modal = new bootstrap.Modal(document.getElementById('addBlockModal'));346 modal.show();347 },348 hideAddBlockModal() {349 const modal = bootstrap.Modal.getInstance(document.getElementById('addBlockModal'));350 modal.hide();351 },352 addBlock(type) {353 let newBlock = {354 type: type,355 expanded: true,356 data: {}357 };358 switch (type) {359 case 'variable':360 newBlock.data = { name: '', value: '' };361 break;362 case 'request':363 newBlock.data = { method: 'GET', url: '', headers: [], body: '' };364 break;365 case 'assertion':366 newBlock.data = { condition: '' };367 break;368 // Otros casos...369 }370 this.blocks.push(newBlock);371 this.hideAddBlockModal();372 },373 executeTest() {374 // Simulación de ejecución de prueba375 this.result = {376 success: true,377 status: 'COMPLETED',378 message: 'Prueba ejecutada exitosamente.'379 };380 // Animación de scroll al resultado381 this.$nextTick(() => {382 const resultBlock = document.querySelector('.block:last-child');383 resultBlock.scrollIntoView({ behavior: 'smooth' });384 });385 }386 },387 mounted() {388 // Inicializar el tema según las preferencias del usuario389 const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;390 if (prefersDark) {391 this.toggleTheme();392 }393 }394 }).mount('#app');395 396 // Manejo del botón de cambio de tema fuera de Vue397 document.getElementById('themeToggle').addEventListener('click', () => {398 document.body.classList.toggle('bg-dark');399 document.body.classList.toggle('text-white');400 const icon = document.getElementById('themeToggle').querySelector('i');401 icon.classList.toggle('bi-moon-fill');402 icon.classList.toggle('bi-sun-fill');403 });404 </script>405 </body>406 </html>407
Enlace
El enlace para compartir es: