Na tentativa de fazer algo semelhante, uma vez lançamos um simulador web no Yandex MVP, no qual o usuário podia escrever código, scripts e tudo mais em guias diferentes, e na porta ao lado ele mostrou tudo isso como o resultado final.

O MVP provou ser bom e trouxemos o simulador da web ao nível de uma ferramenta completa para testar o conhecimento de nossos alunos em Yandex.Practice . Meu nome é Artem, e contarei como fizemos um simulador para ensinar desenvolvimento web, como funciona e o que pode fazer.
Do lado de fora, parece que tudo é simples aqui - coloquei todo o código personalizado em um iframe, postei por postmessage, renderizei de qualquer maneira possível e tudo funcionou. Uma visualização de código online levemente bombeada.
Mas existem nuances.
Como funciona
No início, observamos um possível problema: se você implantar o simulador em um domínio Yandex (como o próprio Workshop, por exemplo), há uma probabilidade diferente de zero de que os usuários se tornem um pouco mais curiosos. Ou seja, eles pegam e jogam algum código no simulador, que o simulador processa com entusiasmo. E o código será fraudulento e retirará o cookie Yandex existente, inserirá em algum serviço de terceiros, após o qual o fraudador terá acesso à conta pessoal do usuário no Yandex e todos os dados pessoais. É muito fácil implementar isso se esse iframe estiver localizado no domínio yandex.ru. Portanto, criamos um domínio separado em yandex.net especificamente para o simulador e o batizamos de Feynman. Em homenagem a Richard , sim.
Em geral, nosso simulador armazena os arquivos que enviamos para o backend em texto simples, formatos json e base64 para imagens. Em seguida, eles são convertidos em arquivos reais e distribuídos já na forma de estática, que podemos colocar em um iframe para renderização.
Mas não estamos apenas jogando o realce de sintaxe aqui, temos um simulador para testar o conhecimento. Portanto, precisamos testar e verificar esse código na hora, ou seja, de alguma forma entrar no processo do iframe e ver se o usuário fez tudo certo, digamos, como ele nomeou a variável, ou se está tudo certo com os divs.
E aqui novamente encontramos domínios. O código do usuário, como já escrevi, é colocado no simulador no domínio Feynman e nós o verificamos no Yandex, no domínio praktikum.yandex.ru . A política de mesma origem do navegador está em guarda e não permite que você adultere as partes internas do iframe se seus domínios forem diferentes.
Portanto, decidimos inserir o iframe no iframe.

A seguinte situação acabou:
- Criamos um iframe, que é inicialmente vazio.
- Ele desenha uma espécie de página em branco.
- Nós, de nossa primeira página, postamos uma mensagem com um link para o que Feynman nos deu (de onde ele hospeda a estática).
- O primeiro iframe pega esse link e o substitui no src do iframe interno.
Como resultado, nosso primeiro iframe pode possuir o código e fazer o que quiser com o iframe interno. Na verdade, os testes são apenas uma função eval que tem acesso a: documento, janela e assim por diante, tudo no iframe. Isso nos dá a oportunidade de fazer um teste para um problema e executá-lo na janela de um determinado iframe.
Não apenas por testes
Em seguida, queríamos adicionar alguns recursos úteis: um terminal, um console, a capacidade de exibir dados do usuário sobre o que ele escreveu no log e outras alegrias. Claro, criamos um modo adaptativo completo para que o usuário possa ver como o resultado ficará em smartphones e tablets.

Para isso, foi escrita uma biblioteca especial que carrega todos os estilos necessários e emula o modo responsivo. Também alteramos um pouco nosso iframe original e adicionamos tudo que permite ao usuário exibir seu console.log na tela, e não apenas alguns objetos simples, mas também árvores dom de documento completas.
Além disso, aprendemos como executar testes preliminares. Isso é útil porque há muitos testes que verificam as mesmas coisas - por exemplo, se o usuário exagerou com loops no código, se ele se empolgou com o aninhamento e assim por diante. Não faz muito sentido descrever isso em cada teste separadamente, portanto, escrevemos uma biblioteca de teste que tem um conjunto de métodos de pré-teste especiais que verificam o código. Se tudo estiver bem nesta fase, o teste principal e os problemas resolvidos já estão verificados, após o que o resultado é mostrado ao usuário.
Para retornar o resultado do teste preliminar para o usuário, também usamos o postmessage - enviamos mensagens através dele, se houver algum erro ou se está tudo bem. A propósito, o código do aluno no simulador da web também é verificado por meio do linter es-lint com tradução para o russo e sempre destaca os erros de sintaxe.
Problemas com revisões de código (e não só)
Se houvesse alguma notificação de sistema ou navegador na página, por exemplo, sugeria-se inserir algo, então, frequentemente, ao executar nosso teste, o usuário continuava a ver as janelas do navegador com notificações e solicitações de entrada de dados. Tínhamos que fazer isso: quando um usuário apenas inicia uma página com seu código para ver como tudo funciona, esse alerta também precisa funcionar. E quando o teste executa esse código para verificação, não precisamos mais de alertas para continuar aparecendo na tela do usuário. Na verdade, substituímos todos esses alertas por nossos próprios stubs para testes (simulação), substituindo alerta, prompt, confirmação dentro da janela. Se você não fizer isso, poderá obter um loop na saída ou um alerta vazio que não faz nada.
A propósito, sobre loops infinitos. O principal problema aqui era que o usuário podia pegar e escrever conscientemente um código que entraria feliz em um loop infinito (há apenas um thread de javascript no navegador) e, como resultado, o navegador inteiro parou.
Para combater isso, aprendemos primeiro a rastrear esses loops infinitos antes de enviar o código para revisão. Para fazer isso, foi necessário refazer o script do usuário de alguma forma, nós seguimos desta forma:
- Para cada ciclo, adicionamos uma determinada função que conta o número de chamadas.
- Se esse número de chamadas exceder 100.000, então imediatamente lançamos uma exceção, que também enviamos de volta por mensagem postal. Além disso, por precaução, verificamos o tempo limite se o ciclo for executado por mais de 10 segundos.
- Ao longo do caminho, rastreamos que, uma vez que surgiu uma exceção, algo está errado aqui e o teste em si não faz mais sentido para ser executado - o código é executado em loop.
A situação com links deve ser anotada separadamente. Digamos que um usuário dentro de seu código possa ter alguns links que devem abrir ao clicar em uma nova guia, por exemplo, seu portfólio ou conta no github. E não precisávamos que esses links abrissem diretamente dentro do iframe - caso contrário, em vez do iframe, teremos uma página com seu link. É necessário abrir essas coisas em uma nova aba, via Aba. Normalmente, para abrir um link não dentro do quadro, mas no quadro pai, você só precisa especificar target = "_ parent". Mas, em nosso caso, precisamos adicionar um manipulador que determina se o link é externo.
E para todos os links, escrevemos um manipulador especial: se vemos que o link é externo, enviamos a postmessage para fora, interrompemos o próprio manipulador de link (evita o padrão) e a postmessage volta à nossa frente. Vemos que temos um link externo aqui e mostramos uma notificação - temos certeza de que estamos indo para um site externo? E depois disso, abrimos novas guias.
E também âncoras, com elas tudo era muito mais direto. Eles simplesmente não funcionavam dentro do iframe. Geralmente. Portanto, como um pequeno hack, nos inscrevemos para clicar em eventos em qualquer link - se houvesse uma âncora nele, criamos o scrollIntoView para um elemento específico.
Todos os metadados (se o usuário tiver um favicon cadastrado na página HTML, por exemplo, ou um título específico), também enviamos via postmessage após o carregamento do iframe. Usando querySelector, obtemos essas duas tags, as enviamos de volta para nossa frente por meio de postmessage e a própria frente insere todos esses ícones quando necessário. Parece uma bagatela, mas o usuário tem a impressão de que possui um navegador completo dentro do navegador.
Tenta contornar o simulador
Nosso simulador da web, ao contrário dos simuladores que criamos para Python, SQL e outros, usa a frente, não o back-end, para verificações. Portanto, quando o usuário conclui os testes corretamente, uma solicitação POST correspondente é enviada ao back-end. Em princípio, um usuário, com a habilidade adequada, pode fazer o mesmo e enviar essa solicitação manualmente.
Existe uma espada de dois gumes. Por um lado, é legal que uma pessoa esteja suficientemente interessada em tecnologia e hacks básicos para fazer isso. Por outro lado, é um pouco como um tiro na perna, pois nosso simulador não serve para receber formalmente dele "OK, você está ótimo em geral, você fez tudo", mas para aprender a trabalhar normalmente, observe seus erros e corrija-os. Em geral, é como ir à academia, sentar no supino por 5 minutos e depois escrever no Facebook “Fiz 3 séries com cem quilos”: dá para divertir a autoestima, mas as conquistas param por aí.
Na verdade, é por isso que não estamos levando essa verificação para o backend, isso resolveria um problema semelhante. As pessoas vêm estudar para conseguir um emprego de verdade (talvez na própria Oficina), não por conquistas virtuais.
Estamos constantemente melhorando o simulador web, usando tanto nossa própria lista de desejos quanto feedback dos usuários, por isso continuaremos a informá-lo sobre seu desenvolvimento. Agora está sendo finalizado, levando em consideração as necessidades dos alunos com solicitação de tecnologias específicas, por exemplo, adicionamos trabalhos com React e NodeJS. O simulador da web é de longe o mais popular de todos, seguido pelo simulador Python - em grande parte devido ao limite inferior de entrada e à popularidade das próprias tecnologias. Além da parte técnica, também existem muitas mecânicas dentro do simulador para trabalhar a teoria interativa (e são suficientes em todos os nossos cursos). Não existe um simulador separado apenas para a especialidade QA, onde fizemos um conjunto especial de questionários + suportes nos quais os testadores estudam. A propósito, alguns testadores,que agora estão nos ajudando a fazer o Workshop são graduados de nossoCurso de controle de qualidade .
Os simuladores para C ++ e o simulador para aprendizado de máquina são mais complicados, se você tiver interesse, tentaremos falar sobre eles nos próximos posts.
Obrigado pela leitura, se você tiver alguma dúvida sobre nossos simuladores ou o Workshop em geral - escreva, nós responderemos.