Запись и воспроизведение Javascript

#javascript #dom-events

#javascript #dom-события

Вопрос:

Я знаю, что можно записывать движения мыши, прокрутку и нажатия клавиш. Но как насчет изменений в документе? Как я могу записать изменения в документ?

Вот моя попытка. Должен быть лучший, более простой способ хранения всех событий?

Я благодарен за все советы, которые я могу получить!

 <!DOCTYPE html>
<html>
<head>
<title>Record And replay javascript</title>
</head>
<body id="if_no_other_id_exist">

<div style="height:100px;background:#0F0" id="test1">click me</div>
<div style="height:100px;background:#9F9" class="test2">click me</div>
<div style="height:100px;background:#3F9" id="test3">click me</div>
<div style="height:100px;background:#F96" id="test4">click me</div>



<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>

$(document).ready(function() {
var the_time_document_is_redy = new Date().getTime();
var the_replay = '';


$('div').live("click", function (){
var the_length_of_visit = new Date().getTime() - the_time_document_is_redy;


// check if the element that is clicked has an id
if (this.id)
{

the_replay =
the_replay
 
"setTimeout("$('#"
 
this.id
 
"').trigger('click')","
 
the_length_of_visit
 
");"
;


alert (
"The following javascript will be included in the file in the replay version:nn"
  
the_replay
) // end alert

} // end if



// if it does not have an id, check if the element that is clicked has an class
else if (this.className)
{

// find the closest id to better target the element (needed in my application)
var closest_div_with_id = $(this).closest('[id]').attr('id');

the_replay =
the_replay
 
"setTimeout("$('#"
 
closest_div_with_id
 
" ."
 
this.className
 
"').trigger('click')","
 
the_length_of_visit
 
");"
;


alert (
"The following javascript will be included in the file in the replay version:nn"
  
the_replay
) // end alert

} // end if

});






// fall back if there are no other id's
$('body').attr('id','if_no_other_id_exist');


// example of how it will work in the replay version
setTimeout("$('#test1').trigger('click')",10000);

});
</script>
</body>
</html>
  

Ответ №1:

Этот вопрос заинтересовал меня, и я реализовал здесь доказательство концепции

https://codesandbox.io/s/jquery-playground-y46pv?fontsize=14amp;hidenavigation=1amp;theme=dark

Используя демонстрационный

  • Нажмите record, щелкните по некоторым кружочкам, введите что-нибудь на входе, снова нажмите record, чтобы остановить запись, и, наконец, нажмите play.
  • Вы можете настроить размер воспроизведения, отредактировав REPLAY_SCALE переменную в исходном коде.
  • Вы можете управлять скоростью воспроизведения, изменяя SPEED переменную в исходном коде.
  • Обратите внимание, я тестировал это только в Chrome.

Подробности реализации:

  • Он отслеживает события перемещения мыши, щелчка и ввода. Он должен быть легко расширяемым для добавления других, таких как прокрутка, изменение размера окна, наведение курсора, фокусировка и т.д.
  • При воспроизведении создается <iframe> исходный HTML-код и воспроизводятся пользовательские события.
  • Прослушиватели событий обходят любые event.stopPropagation() , используя захват при прослушивании событий в документе.
  • Отображение воспроизведения в другом разрешении выполняется с помощью zoom CSS3.
  • Для рисования линий трассировки мыши можно наложить прозрачное полотно. Я использую только простой div, поэтому никаких строк трассировки.

Соображения:

  • Представьте, что мы фиксируем пользовательские события на реальном веб-сайте. Поскольку обслуживаемая страница может измениться между моментом «сейчас» и воспроизведением, мы не можем полагаться на сервер клиента при воспроизведении записи в iframe. Вместо этого мы должны сделать снимок html, всех запросов ajax и запросов ресурсов, сделанных во время записи. В демо я только делаю снимок HTML для простоты. Однако на практике все дополнительные запросы должны храниться на сервере в режиме реального времени по мере их загрузки на странице клиента. Кроме того, во время воспроизведения важно, чтобы запросы воспроизводились с той же временной шкалой, с которой они были восприняты пользователем. Чтобы имитировать временную шкалу запроса, смещение и продолжительность каждого запроса также должны быть сохранены. Загрузка всех запросов страницы по мере их загрузки на клиент приведет к замедлению работы клиентской страницы. Одним из способов оптимизировать эту загрузку может быть хэширование содержимого запроса перед их загрузкой, если хэш уже присутствует на сервере, данные запроса не нужно загружать повторно. Кроме того, сеанс одного пользователя может использовать данные запроса, загруженные другим пользователем, используя этот метод хэширования. Наконец, самому браузеру не нужно выполнять загрузку, при условии, что все запросы проходят через центральный сервер, этот снимок может выполняться на стороне сервера, чтобы не влиять на работу пользователя.

  • При загрузке всех пользовательских событий потребуется тщательное рассмотрение. Поскольку будет сгенерировано много событий, это означает много данных. Возможно, можно было бы произвести некоторое сжатие событий, например, потерять некоторые из менее важных событий mousemove. Запрос на загрузку не должен выполняться для каждого события, чтобы минимизировать количество запросов. События должны буферизоваться до тех пор, пока не будет достигнут размер буфера или время ожидания перед загрузкой каждого пакета событий. Следует использовать тайм-аут, поскольку пользователь может закрыть страницу в любой момент, что приведет к потере некоторых событий.

  • Во время воспроизведения исходящие запросы POST должны быть обработаны, чтобы предотвратить дублирование событий в другом месте.

  • Во время воспроизведения пользовательский агент должен быть подделан, но это может быть ненадежно при отображении исходного отображения.

  • Пользовательский код записи может конфликтовать с клиентским кодом. например, jquery. Для предотвращения этого потребуется пространство имен.

  • Могут быть некоторые крайние случаи, когда ввод и нажатие могут не воспроизводить тот же результирующий HTML, что и в оригинале, например, случайные числа, время даты. Для наблюдения за изменениями HTML могут потребоваться наблюдатели за мутациями, хотя они поддерживаются не во всех браузерах. Скриншоты могли бы пригодиться здесь, но могут оказаться OTT.

Комментарии:

1. Что-то изменилось? При воспроизведении примера открывается iframe и быстро отображается какое-то движение, а затем iframe исчезает :-/

2. Кажется, у меня работает. Это приводит к прерыванию воспроизведения после воспроизведения записи.

3. Спасибо @david_adler Обычно я пробую другие браузеры, и ваш комментарий отправил меня с Firefox на Chrome, в котором он работает. Любые эксперты знают, что может быть причиной проблемы в Firefox…

Ответ №2:

Воспроизведение действий пользователя только с помощью Javascript является сложной проблемой.

Прежде всего, вы не можете перемещать курсор мыши, вы также не можете эмулировать наведение курсора мыши. Таким образом, исчезает большая часть пользовательских взаимодействий со страницей.

Во-вторых, действия, однажды записанные, большую часть времени они должны воспроизводиться в среде, отличной от той, в которой они были записаны в первую очередь. Я имею в виду, что вы можете воспроизводить действия на экране с меньшим разрешением, другим клиентским браузером, другим контентом, обслуживаемым на основе воспроизведения файлов cookie браузера и т.д.

Если вы потратите время на изучение доступных сервисов, которые позволяют вам записывать действия посетителей веб-сайта ( http://clicktale.com, http://userfly.com / чтобы назвать несколько), вы увидите, что ни одно из них не способно полностью воспроизвести действия пользователей, особенно когда дело доходит до наведения курсора мыши, ajax, сложных виджетов JS.

Что касается вашего вопроса об обнаружении изменений, внесенных в DOM — как заявил Крис Бискарди в своем ответе, существуют события мутации, которые предназначены для этого. Однако имейте в виду, что они реализованы не в каждом браузере. А именно, IE их не поддерживает (они будут поддерживаться начиная с IE 9, согласно этой записи в блоге на msdn http://blogs.msdn.com/b/ie/archive/2010/03/26/dom-level-3-events-support-in-ie9.aspx ).

Вам может подойти полагаться на эти события, в зависимости от варианта использования.

Что касается «лучшего, более простого способа хранения всех событий«. Существуют другие способы (с точки зрения синтаксиса) прослушивания событий по вашему выбору, однако обработка (= хранение) их не может быть выполнена простым способом, если только вы не хотите сериализовать целые объекты событий, что было бы не очень хорошей идеей, если бы вы отправляли информацию об этих событиях на какой-либо сервер для их хранения. Вы должны осознавать тот факт, что при использовании веб-сайта происходит огромное количество событий, следовательно, огромное количество потенциальных данных, которые необходимо сохранить / отправить на сервер.

Я надеюсь, что я выразился ясно, и вы найдете часть этой информации полезной. Я сам участвовал в проекте, целью которого было сделать то, чего вы пытаетесь достичь, поэтому я знаю, насколько сложным это может стать, как только вы начнете копаться в предмете.

Комментарии:

1. Спасибо за ваше руководство. Некоторые возможности: 1) Вы можете записывать направление мыши, связанное с точкой в «центре вверху» страницы. Тогда размер окна не будет иметь большого значения. Но другие проблемы, на которые вы указали, все еще существуют… Я думаю, что мне лучше продолжить тем же простым способом, что и в моем примере.

Ответ №3:

Я полагаю, вы ищете события мутации.

http://www.w3.org/TR/2000/REC-DOM-Level-2-Events-20001113/events.html#Events-eventgroupings-mutationevents

Вот несколько ресурсов для вас:

http://tobiasz123.wordpress.com/2009/01/19/utilizing-mutation-events-for-automatic-and-persistent-event-attaching/

http://forum.jquery.com/topic/mutation-events-12-1-2010

https://github.com/jollytoad/jquery.mutation-events

Обновить:

В ответ на комментарий, очень, очень простая реализация:

 //callback function
function onNodeInserted(){alert('inserted')}
//add listener to dom(in this case the body tag)
document.body.addEventListener ('DOMNodeInserted', onNodeInserted, false); 
//Add element to dom 
$('<div>test</div>').appendTo('body')
  

Как сказал WTK, вы вступаете на сложную территорию.

Комментарии:

1. Спасибо за ваш ответ. Сложно, я не понимаю, что это такое или как это работает. Хотите показать простую «демонстрацию оповещений», чтобы показать, что с ней возможно?

2. Я добавил некоторый код к своему ответу. События мутации были добавлены на уровне DOM 2 и были созданы с целью обнаружения изменений в DOM.

3. Рад слышать, что реализация помогла

Ответ №4:

Запись

Сохраните исходный DOM страницы, удалите с нее скрипты, а также вам нужно изменить все относительные URL-адреса на абсолютные.

Затем запишите мутации DOM и событие клавиатуры / мыши.

Воспроизведение

Начните с исходного сохраненного DOM, примените изменения и события, используя порядок временных меток.

Фактически, щелчки ничего не сделают, потому что мы удалили все скрипты. но поскольку мы сохранили изменения DOM, мы можем воспроизвести эффект после щелчка.

Комментарии:

1. Хотели бы вы улучшить свой ответ, предоставив пример кода

Ответ №5:

Я нашел эти два решения на github, которые позволяют записывать события, а затем воспроизводить их на удаленном сервере.

https://github.com/ElliotNB/js-replay

и более комплексное решение

https://github.com/rrweb-io/rrweb

https://www.rrweb.io/#demos

В обоих есть демоверсии, которые вы можете попробовать.

Ответ №6:

В последнее время мы можем использовать MutationObserver

MutationObserver предоставляет разработчикам способ реагировать на изменения в DOM. Он разработан как замена событий мутации, определенных в спецификации событий DOM3.

Медленная демонстрация, потому что консоль.сообщение журнала огромно.

 var mutationObserver = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation)
  })
})
mutationObserver.observe(watchme, {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
  attributeOldValue: true,
  characterDataOldValue: true
})  
 <div id="watchme" contenteditable>
  Hello world!
</div>